简介
- Spring是一个开源的免费的框架(容器)
- Spring是一个轻量级的、非入侵式的框架
- 控制反转(IOC),面向切面编程 (AOP)
- 支持事务的处理,支持对框架进行整合
Spring就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架
官网:https://spring.io/projects/spring-framework#overview
官方下载地址:https://repo.spring.io/release/org/springframework/spring/
GitHub:https://github.com/spring-projects/spring-framework
依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.6</version>
</dependency>
Spring组成
七大模块
Spring Core
Core封装包是框架的最基础部分,提供IOC和依赖注入特性。这里的基础概念是BeanFactory,它提供对Factory模式的经典实现来消除对程序性单例模式的需要,并真正地允许你从程序逻辑中分离出依赖关系和配置。
Spring Context
构建于Core封装包基础上的Context封装包,提供了一种框架式的对象访问方法,有些象JNDI注册器。Context封装包的特性得自于Beans封装包,并添加了对国际化(I18N)的支持(例如资源绑定),事件传播,资源装载的方式和Context的透明创建,比如说通过Servlet 容器。
Spring Dao
DAO (Data Access Object)提供了JDBC的抽象层,它可消除冗长的JDBC编码和解析数据库厂商特有的错误代码。并且,JDBC封装包还提供了一种比编程性更好的声明性事务管理方法,不仅仅是实现了特定接口,而且对所有的POJOs(plain old Java objects)都适用。
Spring ORM
ORM 封装包提供了常用的“对象/关系”映射APIs的集成层。其中包括JPA、JDO、hibernate和iBatis 。利用ORM封装包,可以混合使用所有spring提供的特性进行“对象/关系”映射,如前边提到的简单声明性事务管理。
Spring AOP
Spring的AOP 封装包提供了符合AOP Alliance规范的面向方面的编程实现,让你可以定义,例如方法拦截器(method-interceptors)和切点(pointcuts),从逻辑上讲,从而减弱代码的功能耦合,清晰的被分离开。而且,利用source-level的元数据功能,还可以将各种行为信息合并到你的代码中。
Spring Web
Spring中的Web 包提供了基础的针对Web开发的集成特性,例如多方文件上传,利用Servlet listeners进行IOC容器初始化和针对Web的ApplicationContext。当与WebWork或Struts一起使用Spring时,这个包使Spring可与其他框架结合。
Spring Web MVC
Spring中的MVC封装包提供了Web应用的Model-View-Controller(MVC)实现。Spring 的MVC框架并不是仅仅提供一种传统的实现,它提供了一种清晰的分离模型,在领域模型代码和Web Form之间。并且,还可以借助Spring框架的其他特性。
Spring家族
Spring Boot
- 一个快速开发的脚手架
- 基于SpringBoot可以快速的开发单个微服务
- 约定大于配置
SpringCloud
- SpringCloud基于SpringBoot实现
- 提供很多微服务相关工具
IOC(控制反转)
IOC其实是一种设计思想;而实现这种思想的方式有很多
示例1(IOC原型)
原有方式
Dao层接口
package org.dam.dao;
/**
* @Author dam
* @create 2024/1/5 19:30
*/
public interface UserDao {
public void getUser();
}
Dao层接口实现
【UserDaoImpl】
package org.dam.dao.impl;
import org.dam.dao.UserDao;
/**
* @Author dam
* @create 2024/1/5 19:30
*/
public class UserDaoImpl implements UserDao {
@Override
public void getUser() {
System.out.println("获取用户数据");
}
}
【UserDaoMySqlImpl】
package org.dam.dao.impl;
import org.dam.dao.UserDao;
/**
* @Author dam
* @create 2024/1/5 19:39
*/
public class UserDaoMySqlImpl implements UserDao {
@Override
public void getUser() {
System.out.println("MySql获取用户数据");
}
}
【UserDaoOracleImpl】
package org.dam.dao.impl;
import org.dam.dao.UserDao;
/**
* @Author dam
* @create 2024/1/5 19:39
*/
public class UserDaoOracleImpl implements UserDao {
@Override
public void getUser() {
System.out.println("Oracle获取用户数据");
}
}
Service层接口
package org.dam.service;
/**
* @Author dam
* @create 2024/1/5 19:32
*/
public interface UserService {
public void getUser();
}
Service层接口实现类
import org.dam.dao.UserDao;
import org.dam.dao.impl.UserDaoImpl;
import org.dam.service.UserService;
public class UserServiceImpl implements UserService{
// 直接new,后续如果想更换实现的话,非常麻烦
private UserDao userDao = new UserDaoImpl();
@Override
public void getUser() {
userDao.getUser();
}
}
【缺点】
// 直接new,后续如果想更换实现的话,非常麻烦
private UserDao userDao = new UserDaoImpl();
改进
可以赋值UserDao,这样方便更改实现
public class UserServiceImpl implements UserService{
private UserDao userDao;
//利用set方法进行动态的实现值的注入!
public void setUserDao(UserDao userDao){
this.userDao = userDao;
}
@Override
public void getUser() {
userDao.getUser();
}
}
测试
这样就可以更换实现方式
package org.dam;
import org.dam.dao.impl.UserDaoMySqlImpl;
import org.dam.dao.impl.UserDaoOracleImpl;
import org.dam.service.UserService;
import org.dam.service.impl.UserServiceImpl;
import org.junit.Test;
/**
* @Author dam
* @create 2024/1/5 19:38
*/
public class TestMain {
@Test
public void test() {
UserServiceImpl service = new UserServiceImpl();
// 一开始用mysql来实现
service.setUserDao(new UserDaoMySqlImpl());
service.getUser();
// 后面又想用Oracle去实现呢
service.setUserDao(new UserDaoOracleImpl());
service.getUser();
}
}
总结
- 如果按照原来的模式,用户需求一旦发生改变,程序员就需要在业务层实现类里面新创建实例对象;如果代码量大会很麻烦,成本昂贵!(程序是业务层主动创建对象,控制权在程序猿手上)
- 使用set注入后,业务层程序不在具有主动性,而是变成了被动的接收对象,直接给业务层传递一个对象即可,自己不需要new
- 这种思想,从本质上解决了问题;程序猿不再去管理对象的创建了
- 系统的耦合性(模块与模块间的依赖程序)大大降低
IOC本质
控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中,我们使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。
Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从loc容器中取出需要的对象。
采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。
Spring创建对象示例
项目创建
首先创建一个maven项目
导入依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.6</version>
</dependency>
</dependencies>
实体类
public class Hello {
private String str;
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
@Override
public String toString() {
return "Hello{" +
"str='" + str + '\'' +
'}';
}
}
bean配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--使用Spring来创建对象,在Spring这些都称为Bean
类型 变量名 = new 类型();
Hello hello = new Hello();
id = 变量名
class = new的对象
property 相当于给对象中的属性设置一个值!
-->
<bean id="hello" class="com.dam.pojo.Hello">
<property name="str" value="Spring"/>
</bean>
</beans>
测试
public class MyTest {
public static void main(String[] args) {
//获取Spring的上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
//我们的对象现在都在Spring中的管理了,我们需要使用,直接去里面取出来就可以!
Hello hello = (Hello) context.getBean("hello");
System.out.println(hello.toString());
}
}
运行
思考
Hello对象是谁创建的?
Hello对象是由Spring创建的。
Hello对象的属性是怎么设置的?
Hello对象的属性是由Spring容器设置的。
控制: 谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用Spring后,对象是由Spring来创建的。
反转: 程序本身不创建对象,而变成被动的接收对象。
依赖注入: 就是利用set方法来进行注入的。
IOC是一种编程思想,由主动的编程变成被动的接收。其实就是程序员使用Spring创建并管理的对象。
到了现在,我们彻底不用在程序中去改动了,要实现不同的操作,只需要在xml配置文件中进行修改,所谓的IOC,一句话搞定:对象由Spring来创建,管理,装配!
Spring的IOC修改示例1代码
修改bean配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--使用Spring来创建对象,在Spring这些都称为Bean
类型 变量名 = new 类型();
Hello hello = new Hello();
id = 变量名
class = new的对象
property 相当于给对象中的属性设置一个值!
-->
<bean id="oracleImpl" class="com.dam.dao.UserDaoOracleImpl" />
<bean id="mysqlImpl" class="com.dam.dao.UserDaoMysqlImpl" />
<bean id="UserServiceImpl" class="com.dam.Service.UserServiceImpl">
<!--
ref: 引用Spring容器中创建好的对象
value:具体的值(基本数据类型)
-->
<property name="userDao" ref="oracleImpl"></property>
</bean>
</beans>
单元测试
@Test
public void testSpringIOC() {
//拿到Spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
//需要什么,就get什么
UserServiceImpl userServiceImpl = (UserServiceImpl) applicationContext.getBean("UserServiceImpl");
userServiceImpl.getUser();
}
使用了控制反转之后,直接修改配置文件,就可以修改程序
IOC创建对象的方式
在配置文件加载的时候,所有对象都已经初始化了,Spring创建对象是单例
无参构造
<bean id="user" class="org.dam.pojo.User">
<property name="name" value="小明"/>
</bean>
单元测试
@Test
public void testUserShow(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
//在执行getBean的时候, user已经创建好了 , 通过无参构造
User user = (User) applicationContext.getBean("user");
//调用对象的方法 .
user.show();
}
运行,从输出可以得知,Spring帮我们调用了构造方法
有参构造
下标方式
实体类要有相应的构造方法
实体类
public User(String name) {
System.out.println("执行 User 有参构造方法");
this.name = name;
}
bean.xml
<bean id="user" class="org.dam.pojo.User">
<constructor-arg index="0" value="小花" />
</bean>
单元测试
@Test
public void testUserShow(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
//在执行getBean的时候, user已经创建好了 , 通过无参构造
User user = (User) applicationContext.getBean("user");
//调用对象的方法 .
user.show();
}
参数类型方式(不建议使用)
(缺点:如果有多个相同类型的变量,则无法使用)
<bean id="user" class="com.dam.pojo.User">
<constructor-arg type="java.lang.String" value="小明2号"/>
</bean>
参数名方式(常用)
<bean id="user" class="com.dam.pojo.User">
<constructor-arg name="name" value="小明三号" />
</bean>
配置文件说明
取别名
作用就是给一个bean(类对象)起别名:这样的话我们测试执行时,user和userXiaoHua都可以访问创建的类对象
<bean id="user" class="org.dam.pojo.User">
<constructor-arg index="0" value="小花" />
</bean>
<alias name="user" alias="userXiaoHua"/>
使用示例
User user = (User) applicationContext.getBean("userXiaoHua");
Bean配置
- id:bean的唯一标识符,也就是相当于创建的对象名
- class:bean对象所对应的全限定名(包名+类名)
- name:也可以用来给该bean对象起别名,更高级(可以取多个别名,然后用逗号、空格、分号等分隔开)
- property子标签:给对象中的属性设置一个值
<bean id="user" class="com.dam.pojo.User" name="user1, user2">
<property name="name" value="大壮"/>
</bean>
可以通过别名获取对象
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
User daZhuang = (User)context.getBean("user1");
如果不想要强转,可以使用如下方式
User user = context.getBean("user1",User.class);
配置导入
一般用于团队开发使用,它可以将多个配置文件导入合并为一个总的,等到需要加载配置文件的时候只需要加载这一个总的就相当于加载所有配置类
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<import resource="beans1.xml" />
<import resource="beans2.xml" />
<import resource="beans3.xml" />
</beans>
合并之后,可以通过一个配置文件获取所有的bean
依赖注入(DI)
构造器注入
参照IOC创建对象的方式
Set方式注入
- 依赖:bean对象的创建依赖spring容器
- 注入:bean对象的所有属性由容器注入
【Address类】
public class Address {
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
【Student类】
public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobbys;
private Map<String,String> card;
private Set<String> games;
private String wife;
private Properties info;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public String[] getBooks() {
return books;
}
public void setBooks(String[] books) {
this.books = books;
}
public List<String> getHobbys() {
return hobbys;
}
public void setHobbys(List<String> hobbys) {
this.hobbys = hobbys;
}
public Map<String, String> getCard() {
return card;
}
public void setCard(Map<String, String> card) {
this.card = card;
}
public Set<String> getGames() {
return games;
}
public void setGames(Set<String> games) {
this.games = games;
}
public String getWife() {
return wife;
}
public void setWife(String wife) {
this.wife = wife;
}
public Properties getInfo() {
return info;
}
public void setInfo(Properties info) {
this.info = info;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", address=" + address +
", books=" + Arrays.toString(books) +
", hobbys=" + hobbys +
", card=" + card +
", games=" + games +
", wife='" + wife + '\'' +
", info=" + info +
'}';
}
}
【配置文件】
下面展示不同数据类型的注入方式
<bean id="address" class="com.dam.pojo.Address" >
<property name="address" value="河南省"/>
</bean>
<bean id="student" class="com.dam.pojo.Student">
<!-- 普通值注入 -->
<property name="name" value="小明" />
<!-- Bean注入 -->
<property name="address" ref="address"/>
<!-- 数组注入 -->
<property name="books">
<array>
<value>红楼梦</value>
<value>西游记</value>
<value>三国演义</value>
<value>水浒传</value>
</array>
</property>
<!-- List注入 -->
<property name="hobbys">
<list>
<value>代码</value>
<value>篮球</value>
<value>乒乓球</value>
<value>看电影</value>
</list>
</property>
<!-- Map注入 -->
<property name="card">
<map>
<entry key="身份证" value="1111000384" />
<entry key="银行卡" value="222222222222" />
</map>
</property>
<!-- Set注入 -->
<property name="games">
<set>
<value>LOL1</value>
<value>LOL2</value>
<value>LOL3</value>
</set>
</property>
<!-- Null注入 -->
<property name="wife">
<null></null>
</property>
<!-- Properties -->
<property name="info">
<props>
<prop key="学号">110101</prop>
<prop key="性别">男</prop>
<prop key="username">小明</prop>
<prop key="password">11119999</prop>
</props>
</property>
</bean>
c命名空间和p命名空间注入
要使用命令空间,需要在xml的头文件添加如下约束
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
如
p命名空间
不需要在bean标签里面嵌入其他标签就可以给属性赋值了
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- p命名空间注入(无参构造) 可以直接注入属性 property -->
<bean id="user" class="com.dam.pojo.User" p:name="小明" p:age="18"></bean>
</beans>
c命名空间
必须要存在 有参构造方法,才可以使用c命名空间
public User(String name, int age) {
this.name = name;
this.age = age;
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- c命名空间注入(有参构造器) 可以直接注入属性 property -->
<bean id="user2" class="com.dam.pojo.User" c:name="小强" c:age="19" ></bean>
</beans>
给属性赋值的顺序和有参构造的参数顺序是要一致的
Bean作用域
单例模式(Spring默认模式)
<bean id="user2" class="com.dam.pojo.User" c:name="小强" c:age="19" scope="singleton" ></bean>
原型模式
<bean id="user2" class="com.dam.pojo.User" c:name="小强" c:age="19" scope="prototype" ></bean>
每次使用getBean,返回的都是一个新对象
其余
其余的request、session、application等只能在web开发中使用到
- request:在一次请求中存在
- session:保存到session中
- application:全局
Bean的自动装配(autowire)
- 自动装配是Spring满足Bean依赖的一种方式。
- Spring会在上下文中自动寻找,并自动给bean装配属性。
Spring的三种装配方式
- 在xml中显式的配置
- 在java中显示配置
- 隐式的自动装配bean【重要】
普通装配
有三个类People、Cat、Dog
【Cat】
public class Cat {
public void shout() {
System.out.println("miao~");
}
}
【Dog】
public class Dog {
public void shout(){
System.out.println("wow~");
}
}
【People】
public class People {
private Cat cat;
private Dog dog;
private String name;
public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
【bean】
<bean id="cat" class="com.dam.pojo.Cat"></bean>
<bean id="dog" class="com.dam.pojo.Dog"></bean>
<bean id="people" class="com.dam.pojo.People">
<property name="name" value="小明"></property>
<property name="cat" ref="cat"></property>
<property name="dog" ref="dog"></property>
</bean>
cat 和 dog 对象会装配到 people 对象中
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
People people = context.getBean("people", People.class);
people.getCat().shout();
people.getDog().shout();
}
ByName自动装配
byName:会自动在容器上下文中查找和当前对象set方法后面的值对应的bean id的对象装配到当前对象中
弊端:bean标签里面的id只能取属性名Xx(setXx),其他的不能识别;也就是说一个实体类属性只能对应一个setXx方法,那么也就一个bean
<bean id="cat" class="com.dam.pojo.Cat"></bean>
<bean id="dog" class="com.dam.pojo.Dog"></bean>
<bean id="people" class="com.dam.pojo.People" autowire="byName">
<property name="name" value="小明"></property>
</bean>
People类中有setCat和setDog方法,会自动去找id为cat和dog的Bean
模拟id和属性值不匹配
ByType自动装配
通过byType可以注入成功
byType:会自动在容器上下文中查找,和自己对象属性类型相同的bean,需要保证属性类型全局唯一 。
弊端:通过实体类中的每个属性的类型来在容器中进行上下文的查找;也就是说一个属性类型只能对应一个bean(id可省略,因为根据属性的类型进行查找,不是根据id)
Cat和Dog的id省略也可以注入成功
<bean class="com.dam.pojo.Cat"></bean>
<bean class="com.dam.pojo.Dog"></bean>
<bean id="people" class="com.dam.pojo.People" autowire="byType">
<property name="name" value="小明"></property>
</bean>
【总结】
- byName自动装配:保证所有的bean的id唯一,id与setXx中的Xx一致
- byType自动装配:保证所有的class唯一,class与属性类型一致
使用注解实现自动装配
使用注解需要以下步骤:
- 导入约束 context约束。
- 开启支持 context:annotation-config
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--开启注解支持-->
<context:annotation-config/>
</beans>
说明
在spring4之后,想要使用注解形式,必须得要引入aop的包,详情请看下一节
@Autowired和@Resource
开启之后,可以直接通过注解来注入cat和dog
如果@Autowired自动装配的环境比较复杂,自动装配无法通过一个注解【@Autowired】完成的时候、我们可以
使用@Qualifier(value=“xxx”)去配置@Autowired的使用,指定一个唯一的bean对象注入!
Resource注解:也可以实现装配,但是性能会差一点
【@Autowired和@Resource区别】
- 都是用来自动装配的,都可以放在属性字段上
- @ Autowired 通过byType的方式实现,而且必须要求这个对象存在!默认通过byType方式,当匹配到多个同类型时,再通过byName方式【常用】
- @ Resource 默认通过byname的方式实现,如果找不到名字,则通过byType实现!如果两个都找不到的情况下,就报错!【常用】
- 执行顺序不同:@Autowired默认先通过byType的方式实现,@Resource默认先通过byName方式实现
【注意】
当属性上面加了注解之后,不要再给实体类的属性添加set方法
@Nullable
当字段标记了这个注解,表明该字段可以为null
Spring注解开发
在Spring4之后,使用注解开发都要导入aop的jar包
使用注解需要导入约束,开启注解支持
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--指定要扫描的包,这个包下的注解就会生效-->
<context:component-scan base-package="com.dam.pojo"/>
<!--开启注解支持!-->
<context:annotation-config/>
</beans>
Bean对象注解@Component
实体类中使用@Component之后,不需要在xml中写bean了
//@Component组件
//等价于<bean id="user" class="com.zhang.pojo.User"></bean>
@Component
public class User {
public String name="小明";
}
Bean属性注解@Value
@Value注解用于给实体类的属性赋值,放在属性上或者属性对应的setXx方法上都可以
// @Value("小明")
public String name;
@Value("小明")
public void setName(String name) {
this.name = name;
}
@Value() 相当于
Web开发衍生注解
@Component注解有几个衍生注解,我们在web开发中,会按照mvc架构进行分层
- dao层:@Repository
- service层:@Service
- controller层:@Controller
这四个注解的功能都是一样的,都代表将某个类注册到Spring中,来装配bean
要让注解生效,需要扫描com.dam下所有的包
<!--指定要扫描的包,这个包下的注解就会生效-->
<context:component-scan base-package="com.dam"/>
作用域**@Scope**
@Scope注解:用来设置实体类的单例模式、原型模式等
@Component
@Scope("singleton")
public class User {
public String name;
@Value("小明")
public void setName(String name) {
this.name = name;
}
}
总结
xml 与注解:
- xml 更加万能,适用于任何场合!维护简单方便
- 注解不是自己类使用不了,维护相对复杂!
xml 与注解最佳实践:
- xml用来管理bean;
- 注解只负责完成属性的注入;
- 我们在使用的过程中,只需要注意一个问题:想让注解生效,就需要开启注解的支持
@Configuration 用JAVA的方式配置Spring(JavaConfig)
现在可以不使用Spring配置文件(applicationContext.xml),而是全部交给java来进行配置。
JavaConfig是Spring的一个子项目,在Spring4之后,它成为了一个核心功能
实体类
@Component
public class User {
@Value("阿花")
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
配置类
@Configuration,类中出现这个表示该类是一个配置类,就相当于beans.xml配置文件
@Configuration
//扫描同一包下的bean
@ComponentScan("com.zhang.pojo")
public class DamConfig {
//相当于给Spring注册一个User的bean对象
//方法名(getUser):代表bean标签的id属性
//方法返回类型(User):代表bean的class属性
//方法返回值(new User()):要注入到bean的对象
@Bean
public User getUser(){
return new User();
}
}
测试
public class MyTest {
@Test
public void test1(){
// 如果完全使用java来配置Spring,
// 那么需要AnnotationConfigApplicationContext来获取Spring容器,
// 通过加载配置类对象的方式
ApplicationContext context = new AnnotationConfigApplicationContext(DamConfig.class);
// “getUser”是java配置类里面对应bean的方法名称
User user = context.getBean("getUser",User.class);
System.out.println(user.getName());
}
}
其他用法
@Configuration可以做xml做的所有事情
扫描包
模拟xml拼接(合并配置类)
代理模式
什么是代理模式
【设计模式——学习笔记】23种设计模式——代理模式Proxy(原理讲解+应用场景介绍+案例介绍+Java代码实现)_Hello Dam的博客-CSDN博客
静态代理
优缺点
优点:
- 操作更加纯粹,直接面向代理对象即可
- 可以在代理角色里添加新的业务
- 实现了业务的分工
- 公共业务发生扩展的时候,方便集中管理
缺点:
- 一个真实角色(客户)就需要一个代理对象,代码量翻倍,开发效率降低
角色分析
- 抽象角色:一般使用接口或者是抽象类来解决
- 真实角色:被代理的角色
- 代理角色:代理真实角色,代理真实角色后,我们一般会做些附属操作
- 客户:访问代理角色的人
示例(中介代理租房)
【租房接口】
public interface Rent {
public void rent();
}
【房东】
//房东
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东要出租房子");
}
}
【中介】
//中介
public class Proxy implements Rent{
private Host host;
public Proxy() {
}
public Proxy(Host host) {
this.host = host;
}
//看房子
public void seeHouse(){
System.out.println("中介带领看房子");
}
//租赁合同
public void hetong(){
System.out.println("签合同");
}
//中介费
public void fare(){
System.out.println("收中介费");
}
@Override
public void rent() {
// 中介除了帮房东租房之外,还可以做一些其他事情
seeHouse();
host.rent();
hetong();
fare();
}
}
【客户】
public class Client {
public static void main(String[] args) {
//房东出租房子
Host host = new Host();
//代理帮中介出租,但 代理角色会有 附属操作
Proxy proxy = new Proxy(host);
//找中介租房
proxy.rent();
}
}
示例(给接口实现类添加日志)
【接口】
public interface UserService {
public void add();
public void del();
public void update();
public void select();
}
【接口实现类】
//真实对象
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加对象");
}
@Override
public void del() {
System.out.println("删除对象");
}
@Override
public void update() {
System.out.println("更改对象");
}
@Override
public void select() {
System.out.println("查询对象");
}
}
【代理角色】
在继承UserService的基础上(不改变原有代码的基础上,增加了附加操作【log日志功能】)
public class UserServiceProxy implements UserService{
UserService userService;
public void setUserService(UserServiceImpl userService){
this.userService = userService;
}
@Override
public void add() {
log("add");
System.out.println("add方法");
}
@Override
public void del() {
log("del");
System.out.println("del方法");
}
@Override
public void update() {
log("update");
System.out.println("update方法");
}
@Override
public void select() {
log("select");
System.out.println("select方法");
}
public void log(String msg){
System.out.println("使用了"+msg+"方法");
}
}
【客户端访问】
public class Client {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
UserServiceProxy proxy = new UserServiceProxy();
proxy.setUserService(userService);
proxy.add();
}
}
动态代理
介绍
- 动态代理和静态代理角色一样。
- 动态代理的代理类是动态生成的,不是直接写好的,可以解决代码量翻倍问题
- 动态代理分类:基于接口 的动态代理( JDK动态代理)、基于类的动态代理(cglib)
优点
- 静态代理的所有优点
- 一个动态代理类代理的是一个接口,一般就是一个对应的业务
- 一个动态代理类可以代理多个类,只要是实现了同一个接口即可
示例
两个重要的类
- Proxy:代理
- InvocationHandler:调用处理程序
【接口】
public interface Rent {
public void rent();
}
【真实角色】
//房东
public class Host implements Rent {
@Override
public void rent() {
System.out.println("房东要出租房子");
}
}
【动态代理类】
//用这个类自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口
private Rent rent;
public void setRent(Rent rent){
this.rent = rent;
}
//生成得到代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(),this);
}
// 处理代理实例,返回结果
// 调用被代理对象的方法的时候,会默认执行这个方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//动态代理的本质,就是使用反射机制实现
seeHouse();
Object result = method.invoke(rent,args);
return result;
}
public void seeHouse(){
System.out.println("中介带看房子");
}
}
【客户端】
public static void main(String[] args) {
//真实角色
Host host = new Host();
//代理角色
ProxyInvocationHandler handler = new ProxyInvocationHandler();
//通过调用程序处理需要调用的接口对象
handler.setRent(host);
//proxy就是动态生成,我们并没有写它
Rent proxy = (Rent) handler.getProxy();
proxy.rent();
}
动态代理工具类模板
//用这个类自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口
private Object target;
public void setTarget(Object target){
this.target = target;
}
//生成得到代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
}
//处理代理实例,返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//打印方法名称
log(method.getName());
//动态代理的本质,就是使用反射机制实现
Object result = method.invoke(target,args);
return result;
}
public void log(String msg){
System.out.println("执行了"+msg+"方法");
}
}
【客户端调用】
public static void main(String[] args) {
//真实角色
UserServiceImpl userService = new UserServiceImpl();
//代理角色,不存在
ProxyInvocationHandler phandler = new ProxyInvocationHandler();
//设置要代理的对象
phandler.setTarget(userService);
//动态生成代理类
UserService proxy = (UserService) phandler.getProxy();
proxy.add();
}
代理模式在Spring中的作用
AOP的底层就是通过代理模式完成
AOP
什么是AOP
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP在Spring中的作用
提供声明式事务;允许用户自定义切面
- 横切关注点:跨越应用程序多个模块的方法或功能。与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志,安全,缓存,事务等等…
- 切面(ASPECT):横切关注点被模块化的特殊对象。即,它是一个类
- 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法
- 目标(Target):被通知对象
- 代理(Proxy):向目标对象应用通知之后创建的对象
- 切入点(PointCut):切面通知执行的“地点”的定义
- 连接点(JointPoint):与切入点匹配的执行点
SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:就是SpringAOP在不改变原来代码的情况下,去增加新的业务功能。
Spring使用AOP
依赖
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.19</version>
<scope>runtime</scope>
</dependency>
Spring API 接口实现
【接口(UserService)】
public interface UserService {
public void add();
public void del();
public void update();
public void query();
}
【实现类(UserServiceImpl)】
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("add了一个方法");
}
@Override
public void del() {
System.out.println("del了一个方法");
}
@Override
public void update() {
System.out.println("update了一个方法");
}
@Override
public void query() {
System.out.println("query了一个方法");
}
}
【方法执行前日志】
public class Log implements MethodBeforeAdvice {
//method 要执行的目标对象的方法
//objects 参数
// target 目标对象
@Override
public void before(Method method, Object[] rags, Object target) throws Throwable {
System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了");
}
}
【方法执行后日志】
public class AfterLog implements AfterReturningAdvice {
//returnValue 返回值
//method 要执行的目标对象的方法
//objects 参数
// target 目标对象
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了"+method.getName()+"方法被执行,返回结果为"+returnValue);
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--指定要扫描的包,这个包下的注解就会生效-->
<context:component-scan base-package="com.zhang"/>
<!--开启注解支持!-->
<context:annotation-config/>
<!-- 注册 bean -->
<bean id="userService" class="com.zhang.service.UserServiceImpl"/>
<bean id="log" class="com.zhang.log.Log"/>
<bean id="afterLog" class="com.zhang.log.AfterLog"/>
<!-- 配置AOP :需要导入AOP约束 -->
<aop:config>
<!-- 切入点 expression表达式 execution(要执行的位置 修饰词 返回值 类名 方法名 参数) -->
<aop:pointcut id="pointcut" expression="execution(* com.zhang.service.UserServiceImpl.*(..))"/>
<!-- 执行环绕增加日志方法 -->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"></aop:advisor>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"></aop:advisor>
</aop:config>
</beans>
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//!!!!!!!!!!!!!!!动态代理的是接口:!!!!!!!!!!!!!!!!!!!!
//所以应该是UserService,而不是UserServiceImpl
UserService userService = context.getBean("userService", UserService.class);
userService.query();
}
自定义实现AOP(切面定义)
【DiyPointCut类】
public void before(){
System.out.println("方法执行前");
}
public void after(){
System.out.println("方法执行后");
}
【applicationContext.xml】
<!-- 方式二:自定义来实现AOP【主要是切面定义】 -->
<bean id="diy" class="com.zhang.diy.DiyPointCut">
</bean>
<aop:config>
<!-- 切面 ref 引用的类 -->
<aop:aspect ref="diy">
<!-- 切入点 -->
<aop:pointcut id="point" expression="execution(* com.zhang.service.UserServiceImpl.*(..))"/>
<!-- 通知 -->
<aop:before method="before" pointcut-ref="point"></aop:before>
<aop:after method="after" pointcut-ref="point"></aop:after>
</aop:aspect>
</aop:config>
【测试】
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//!!!!!!!!!!!!!!!动态代理的是接口:!!!!!!!!!!!!!!!!!!!!
//所以应该是UserService,而不是UserServiceImpl
UserService userService = context.getBean("userService", UserService.class);
userService.query();
}
使用注解来实现
依赖
<!-- https://mvnrepository.com/artifact/aspectj/aspectjrt -->
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.5.4</version>
</dependency>
实现
【AnnotationPointCut】
导包别导错了
@Aspect 标注这个类是一个切面
@Aspect
public class AnnotationPointCut {
@Before("execution(* com.zhang.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("======方法执行前=====");
}
@After("execution(* com.zhang.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("======方法执行后=====");
}
//在环绕增强中,可以给定一个参数,代表我们要获取处理切入的点
@Around("execution(* com.zhang.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
// 获得签名
// Signature signature = jp.getSignature();
// System.out.println("signature::::"+signature);
// System.out.println(jp.proceed());
//执行方法
Object proceed = jp.proceed(); //执行方法
System.out.println("环绕后");
}
}
【applicationContext.xml】
<!-- 方式三:使用注解来实现 -->
<bean id="annotationPointCut" class="com.zhang.diy.AnnotationPointCut"></bean>
<!-- 开启注解支持 默认JDK(proxy-target-class="false") cglib(proxy-target-class="true") -->
<aop:aspectj-autoproxy proxy-target-class="true" />
【执行结果】
Spring整合Mybatis
依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.dam</groupId>
<artifactId>spring-study</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Spring相关-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.6</version>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<!-- AOP -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<!-- Spring整合Mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
</dependencies>
<build>
<!--静态资源过滤-->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<!--不写下面的配置,无法获取到mybatis-config.xml-->
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
</project>
MyBatis-Spring介绍
MyBatis-Spring会帮助你将MyBatis代码无缝地整合到Spring中。它将允许MyBatis参与到Spring的事务管理之中,创建映射器mapper和Sql Session并注入到bean中,以及将Mybatis的异常转换为Spring的DataAccessException。最终,可以做到应用代码不依赖于MyBatis、Spring或MyBatis-Spring。
Mybatis的事务管理很笨重,可以用Spring的AOP,使用MyBatis-Spring需要版本匹配
整合方式一
创建mybatis-config.xml
这个配置文件并不需要是一个完整的 MyBatis 配置。
- 任何环境配置(),数据源()和 MyBatis 的事务管理器()都会被忽略。SqlSessionFactoryBean 会创建它自有的 MyBatis 环境配置(Environment),并按要求设置自定义环境的值
- 之所以还需要这个配置文件,是因为它在需要修改 MyBatis 的基础配置非常有用,如 < settings> 或 < typeAliases>元素
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--核心配置文件-->
<configuration>
<!--取别名-->
<typeAliases>
<package name="org.dam.pojo"/>
</typeAliases>
</configuration>
创建Spring-dao.xml
最好是让spring-dao.xml
专门处理数据库,其他类的注入放在applicationContext.xml
或bean.xml
中,如下图的方式
要使用 Spring 整合 MyBatis,需要在 Spring 应用上下文中定义至少两样东西
- SqlSessionFactory
- 至少一个数据映射器类
在 Mybatis-Spring 中,可使用SqlSessionFactoryBean来创建 SqlSessionFactory。要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>
- 在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建
- 在 MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession。一旦你获得一个 session 之后,你可以使用它来执行映射了的语句,提交或回滚连接,最后当不再需要它的时候,可以关闭 session
- SqlSessionFactory有一个唯一的必要属性:用于 JDBC 的 DataSource(数据源)。这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了
- 还有一个常用的属性是 configLocation,它用来指定 MyBatis 的 XML 配置文件路径
- SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。模板可以参与到 Spring 的事务管理中,并且由于其是线程安全的,可以供多个映射器类使用,你应该总是用 SqlSessionTemplate 来替换 MyBatis 默认的 DefaultSqlSession 实现。在同一应用程序中的不同类之间混杂使用可能会引起数据一致性的问题。
- 可以使用 SqlSessionFactory 作为构造方法的参数来创建 SqlSessionTemplate 对象。
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
- 现在,这个 bean 就可以直接注入到你的 DAO bean 中了。你需要在你的 bean 中添加一个 SqlSession 属性,就像下面这样:
public class UserDaoImpl implements UserDao {
private SqlSession sqlSession;
public void setSqlSession(SqlSession sqlSession) {
this.sqlSession = sqlSession;
}
public User getUser(String userId) {
return sqlSession.getMapper...;
}
}
- 按下面这样,注入 SqlSessionTemplate
<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl">
<property name="sqlSession" ref="sqlSession" />
</bean>
最终的完整Spring-dao.xml
文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置数据源:数据源有非常多,可以使用第三方的,也可使使用Spring的-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis-study?useSSL=true&useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="12345678"/>
</bean>
<!--配置SqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--关联Mybatis配置文件-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!--指定mapper的xml文件所在地址-->
<property name="mapperLocations" value="classpath:org/dam/dao/xml/*.xml"/>
</bean>
<!--注册sqlSessionTemplate , 关联sqlSessionFactory-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--利用构造器注入-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
<bean id="userMapper" class="org.dam.dao.impl.UserMapperImpl">
<!--将sqlSession注入到UserMapper实现类中,供其使用-->
<property name="sqlSession" ref="sqlSession"/>
</bean>
</beans>
实体类
package org.dam.pojo;
import lombok.ToString;
/**
* @Author dam
* @create 2024/1/5 19:54
*/
@ToString
public class User {
private String name;
public User() {
System.out.println("执行 User 构造方法");
}
public User(String name) {
System.out.println("执行 User 有参构造方法");
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("name="+ name );
}
}
Mapper
package org.dam.dao;
import org.dam.pojo.User;
import java.util.List;
/**
* @Author dam
* @create 2024/1/6 10:54
*/
public interface UserMapper {
public List<User> selectUser();
}
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dam.dao.UserMapper">
<select id="selectUser" resultType="User">
select * from user
</select>
</mapper>
impl
package org.dam.dao.impl;
import org.dam.dao.UserMapper;
import org.dam.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;
import java.util.List;
/**
* @Author dam
* @create 2024/1/6 11:03
*/
public class UserMapperImpl implements UserMapper {
//sqlSession不用我们自己创建了,Spring来管理
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public List<User> selectUser() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.selectUser();
}
}
单元测试
@Test
public void testSpringMybatis1(){
ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
UserMapper mapper = (UserMapper) context.getBean("userMapper");
List<User> user = mapper.selectUser();
System.out.println(user);
}
运行
项目文件结构
整合方式二
mybatis-spring1.2.3版以上的才可以使用这种方式:
dao继承Support类 ,直接利用 getSqlSession() 获得 ,然后直接注入SqlSessionFactory。比起方式1 , 不需要管理SqlSessionTemplate ,而且对事务的支持更加友好
Spring-dao.xml
不需要再注册sqlSessionTemplate
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置数据源:数据源有非常多,可以使用第三方的,也可使使用Spring的-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis-study?useSSL=true&useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="12345678"/>
</bean>
<!--配置SqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--关联Mybatis配置文件-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!--指定mapper的xml文件所在地址-->
<property name="mapperLocations" value="classpath:org/dam/dao/xml/*.xml"/>
</bean>
<bean id="userMapper2" class="org.dam.dao.impl.UserMapperImpl2">
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
</beans>
接口实现类
直接继承SqlSessionDaoSupport
,然后调用其getSqlSession()
方法就可以使用sqlSession
package org.dam.dao.impl;
import org.dam.dao.UserMapper;
import org.dam.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import java.util.List;
/**
* @Author dam
* @create 2024/1/6 11:03
*/
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {
@Override
public List<User> selectUser() {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
return mapper.selectUser();
}
}
单元测试
@Test
public void testSpringMybatis2(){
ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
UserMapper mapper = (UserMapper) context.getBean("userMapper2");
List<User> user = mapper.selectUser();
System.out.println(user);
}
Spring事务
事务概念
- 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!
- 事务管理是企业级应用程序开发中必备技术,用来确保数据的完整性和一致性。
事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用。
事务四个属性ACID
- 原子性(atomicity):事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用
- 一致性(consistency):一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中
- 隔离性(isolation):可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏
- 持久性(durability):事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中
Spring在不同的事务管理API之上定义了一个抽象层,使得开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制。Spring支持编程式事务管理和声明式的事务管理。
编程式事务管理
- 将事务管理代码嵌到业务方法中来控制事务的提交和回滚,如执行
session.commit();
- 缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码
声明式事务管理
- 一般情况下比编程式事务好用
- 将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理
- 将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理
Spring事务管理实战
使用Spring管理事务,注意头文件的约束导入 : tx
xmlns:tx=“http://www.springframework.org/schema/tx”
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd"
因为还要使用aop来织入事务,还需要导入aop相关约束
xmlns:aop=“http://www.springframework.org/schema/aop”
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
最终的spring-dao.xml
文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd"
>
<!--配置数据源:数据源有非常多,可以使用第三方的,也可使使用Spring的-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost:3306/mybatis-study?useSSL=true&useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="12345678"/>
</bean>
<!--配置SqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--关联Mybatis配置文件-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!--指定mapper的xml文件所在地址-->
<property name="mapperLocations" value="classpath:org/dam/dao/xml/*.xml"/>
</bean>
<bean id="userMapper2" class="org.dam.dao.impl.UserMapperImpl2">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--配置哪些方法使用什么样的事务,配置事务的传播特性-->
<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="delete" propagation="REQUIRED"/>
<tx:method name="update" propagation="REQUIRED"/>
<tx:method name="search*" propagation="REQUIRED"/>
<tx:method name="get" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--配置aop织入事务 org.dam.dao包下所有类的所有方法中。
这样,在调用这些方法时,会自动实现事务的管理,确保数据的一致性和完整性。-->
<aop:config>
<!--切入点是 org.dam.dao包下的所有类的所有方法-->
<aop:pointcut id="txPointcut" expression="execution(* org.dam.dao.*.*(..))"/>
<!--定义一个advisor(通知器),将事务通知(txAdvice)织入到切入点-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
</beans>
事务管理器
- 无论使用Spring的哪种事务管理策略(编程式或者声明式)事务管理器都是必须的。
- 就是 Spring的核心事务管理抽象,管理封装了一组独立于技术的方法。
事务管理器的Bean
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
事务的通知配置
配置好事务管理器后我们需要去配置事务的通知
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--配置哪些方法使用什么样的事务,配置事务的传播特性-->
<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="delete" propagation="REQUIRED"/>
<tx:method name="update" propagation="REQUIRED"/>
<tx:method name="search*" propagation="REQUIRED"/>
<tx:method name="get" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
spring事务传播特性:
事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为:
propagation_requierd
:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。propagation_supports
:支持当前事务,如果没有当前事务,就以非事务方法执行。propagation_mandatory
:使用当前事务,如果没有当前事务,就抛出异常。propagation_required_new
:新建事务,如果当前存在事务,把当前事务挂起。propagation_not_supported
:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。propagation_never
:以非事务方式执行操作,如果当前事务存在则抛出异常。propagation_nested
:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作
Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。
假设 ServiceX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。几个方法存在调用,会被放在一组事务当中!
配置AOP
导入aop的头文件!
<!--配置aop织入事务 org.dam.dao包下所有类的所有方法中。
这样,在调用这些方法时,会自动实现事务的管理,确保数据的一致性和完整性。-->
<aop:config>
<!--切入点是 org.dam.dao包下的所有类的所有方法-->
<aop:pointcut id="txPointcut" expression="execution(* org.dam.dao.*.*(..))"/>
<!--定义一个advisor(通知器),将事务通知(txAdvice)织入到切入点-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
实体类更新
package org.dam.pojo;
import lombok.AllArgsConstructor;
import lombok.ToString;
/**
* @Author dam
* @create 2024/1/5 19:54
*/
@ToString
@AllArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
public User() {
System.out.println("执行 User 构造方法");
}
public User(String name) {
System.out.println("执行 User 有参构造方法");
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("name="+ name );
}
}
mapper
接口更新
package org.dam.dao;
import org.dam.pojo.User;
import java.util.List;
/**
* @Author dam
* @create 2024/1/6 10:54
*/
public interface UserMapper {
public List<User> selectUser();
//添加一个用户
int addUser(User user);
//根据id删除用户
int deleteUser(int id);
void testTransaction();
}
mapper.xml更新
这里故意将删除语句delete
错误写成deletes
,这样就可以模拟删除动作的错误
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dam.dao.UserMapper">
<select id="selectUser" resultType="User">
select * from user
</select>
<insert id="addUser" parameterType="user">
insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
</insert>
<delete id="deleteUser" parameterType="int">
deletes from user where id = #{id}
</delete>
</mapper>
实现类更新
textTransaction
方法同时调用了
package org.dam.dao.impl;
import org.dam.dao.UserMapper;
import org.dam.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import java.util.List;
/**
* @Author dam
* @create 2024/1/6 11:03
*/
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {
@Override
public List<User> selectUser() {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
return mapper.selectUser();
}
//新增
@Override
public int addUser(User user) {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
return mapper.addUser(user);
}
//删除
@Override
public int deleteUser(int id) {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
return mapper.deleteUser(id);
}
/**
* 事务测试
*/
@Override
public void testTransaction(){
User user = new User(10,"dam","123456");
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
mapper.addUser(user);
mapper.deleteUser(4);
}
}
单元测试
/**
* 事务测试
*/
@Test
public void testSpringMybatis2Transaction(){
ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
UserMapper mapper = (UserMapper) context.getBean("userMapper2");
mapper.textTransaction();
}
执行之后,sql报错了
数据表中也没有增加相应的数据,说明事务管理成功
思考
为什么需要配置事务?
- 如果不配置事务,可能存在数据提交不一致的情况下
- 如果我们不在SPRING中去配置声明式事务,我们就需要在代码中手动配置事务
- 事务在项目的开发中十分重要,设计到数据的一致性和完整性问题,不容马虎!
文章说明
本文章为本人学习 遇见狂神说 的学习笔记,文章中大部分内容来源于狂神的视频【狂神说Java】Spring5最新完整教程IDEA版通俗易懂,也有部分内容来自于自己的思考,发布文章是想帮助其他学习的人更方便地整理自己的笔记或者直接通过文章学习相关知识,如有侵权请联系删除,最后对狂神的优质课程表示感谢。