1、Spring
1.1、 简介
-
Spring:春天—>给软件行业带来了春天!
-
2002年,首次推出了Spring的雏形:interface21!
-
2004年3月24日,Spring框架以interface21框架为基础,经过重新设计,发布了1.0正式版!
-
Spring理念 : 使现有技术更加实用 . 本身就是一个大杂烩 , 整合现有的框架技术。
-
官网 : http://spring.io/
-
官方下载地址 : https://repo.spring.io/libs-release-local/org/springframework/spring/
-
GitHub : https://github.com/spring-projects
-
SSH:Struct2 + Spring + Hibernate
-
SSM: SpringMVC + Spring + Mybatis
导包!
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
1.2、 优点
- Spring是一个开源的免费的框架(容器)!
- Spring是一个轻量级的、非入侵式的框架
- 轻量级:导包就可以用!
- 非入侵式的: 引入Spring后不会对源码有任何的影响,反而用了会更好
- 控制反转(IOC)、面向切面(AOP)
- 支持事务的处理,对框架整合的支持
总结:Spring就是一个轻量级的控制反转(IOC)和面向切面(AOP)的框架!
1.3、 组成
Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式 .
Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式 .
- 核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
- Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
- Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。
- Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
- Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
- Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
- Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
1.4 拓展
现代化的Java开发!说白就是基于Spring的开发!
- Spring Boot
- 一个快速开发的脚手架。
- 基于Spring Boot可以快速的开发单个微服务。
- 约定大于配置!
- Spring Cloud
- Spring Cloud是基于Spring Boot实现的。
学习Spring Boot的前提,需要完全掌握Spring和SpringMVC!承上启下的作用!
弊端:发展了太久之后,违背了原来的理念!配置十分繁琐,人称:配置地狱!
2、IOC理论推导
原来写代码方式
-
UserDao
public interface UserDao { void getUser(); }
-
UserDaoImpl
public class UserDaoImpl implements UserDao{ @Override public void getUser() { System.out.println("默认获取user方法"); } }
-
UserService
public interface UserService { void getUser(); }
-
UserServiceImpl
public class UserServiceImpl implements UserService{ // 这一块的实现类即调用的类写死了 // 如果客户需求有改变,需要我们手动改 private UserDao userDao = new UserDaoImpl(); @Override public void getUser() { userDao.getUser(); } }
-
测试
public class MyTest { @Test public void test(){ UserService userService = new UserServiceImpl(); userService.getUser(); } }
问题
现在客户需求有变化,需要我们使用Mysql、Oracle去获取User
- 则我们需要在Service层去改变UserDao的实现类,修改源代码
- 如果程序代码量十分大,修改一次的成本代价十分昂贵!
我们选择使用一个Set接口去实现,发生了革命性的改变!
只需要客户改变即可,不需要再去修改源码
// Service层
public class UserServiceImpl implements UserService{
private UserDao userDao;
// 使用set进行动态实现值的注入
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void getUser() {
userDao.getUser();
}
}
// 客户层面
public class MyTest {
@Test
public void test(){
UserService userService = new UserServiceImpl();
// userService接口中没有setUserDao()方法 需要强转实现类
((UserServiceImpl)userService).setUserDao(new UserDaoMySQLImpl());
userService.getUser();
}
}
- 之前程序主动创建对象!控制权在程序手中!
- 使用了set注入,程序不再具有主动性,而是被动的接受对象!
这种思想,从本质上解决了问题,我们程序员不用在去管理对象的创建了。系统耦合性大大降低!可以更加专注的在业务上!
这就是IOC的原型!
IOC本质
控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。
IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。
Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。
采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。
3、HelloSpring
实体类
package com.poppy.pojo;
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 + '\'' +
'}';
}
}
beans.xml —> Spring 容器
<?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">
<!--
bean 就是Java对象 由Spring创建和管理
原来的方式
类型 变量名 = new 类型();
变量名 = 值;
现在的方式
class ——> 类型
id ——> 变量名
value ——> 赋值
-->
<bean id="hello" class="com.poppy.pojo.Hello">
<property name="str" value="Spring"/>
</bean>
</beans>
测试
public class MyTest {
@Test
public void test(){
// 获取spring的上下文对象
// 拿到Spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 我们的对象现在都在Spring容器中管理了,需要使用,直接去里面取出来就行了。
Hello hello = (Hello) context.getBean("hello");
System.out.println(hello);
}
}
思考
-
Hello 对象是谁创建的 ?
- hello 对象是由Spring创建的
-
Hello 对象的属性是怎么设置的 ?
- hello 对象的属性是由Spring容器设置的
-
这个过程就叫控制反转 :
-
控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的
-
反转 : 程序本身不创建对象 , 而变成被动的接收对象 .
-
依赖注入 : 就是利用set方法来进行注入的.
-
可以通过new ClassPathXmlApplicationContext()去浏览一下底层源码。
**OK , 到了现在 , 我们彻底不用在程序中去改动了 , 要实现不同的操作 , 只需要在xml配置文件中进行修改 , 所谓的IoC,一句话搞定 : 对象由Spring 来创建 , 管理 , 装配 ! **
改造刚刚的项目
beans.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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 创建 UserDaoImpl UserDaoMySQLImpl UserDaoOracleImpl 对象-->
<bean id="userDaoImpl" class="com.poppy.dao.UserDaoImpl"/>
<bean id="userDaoMySQLImpl" class="com.poppy.dao.UserDaoMySQLImpl"/>
<bean id="userDaoOracleImpl" class="com.poppy.dao.UserDaoOracleImpl"/>
<!--创建service层的对象 UserServiceImpl-->
<bean id="userServiceImpl" class="com.poppy.service.UserServiceImpl">
<!--
value: 赋予一个值
ref: 引用容器里边创建的对象
-->
<property name="userDao" ref="userDaoImpl"/>
</bean>
</beans>
Test
public class MyTest {
@Test
public void test(){
// 拿到容器
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserServiceImpl userServiceImpl = (UserServiceImpl) context.getBean("userServiceImpl");
userServiceImpl.getUser();
}
}
4、 IOC 创建对象方式
无参构造
<bean id="user" class="com.poppy.pojo.User">
<property name = "name" value = "Poppy"/>
</bean>
有参构造
索引构造
<bean id="user" class="com.poppy.pojo.User">
<constructor-arg index="0" value="Poppy"/>
</bean>
类型构造(不推荐)
<bean id="user" class="com.poppy.pojo.User">
<constructor-arg type="java.lang.String" value="Manjuas"/>
</bean>
参数名构造(推荐)
<bean id="user" class="com.poppy.pojo.User">
<constructor-arg name="name" value="狂神说Java"/>
</bean>
总结
在spring容器加载配置文件的时候 (所有的bean 都被实例化了) 或者说 这个容器管理的对象已经初始化了
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
}
5、Spring配置
5.1、 别名
<!--设置别名 也可以通过别名取到 了解即可!-->
<alias name="user" alias="userNew"/>
5.2、 bean的配置
<!--
id : bean 对象的唯一标识符
class : bean 对象类型的全限定名
全限定名 : 包名+类名
name : 别名! 比alias更强大! 可以取多个 且可以使用",", " ", ";"隔开!
如果不配置id 和 name 可以通过反射获取对象
-->
<bean id="user2" class="com.poppy.pojo.User" name="user3,u2 u4;u5">
<property name="name" value="Poppy"/>
</bean>
5.3、 import
import 一般用于团队开发 将所有的xml配置文件合在一块, 通过import标签!
<import resource="beans.xml"/>
<import resource="beans2.xml"/>
6、依赖注入DI(Dependency Injection)
6.1、 构造器注入(前边讲过)
<bean id="user" class="com.poppy.pojo.User">
<constructor-arg name="name" value="狂神说Java"/>
</bean>
6.2、 Set注入【重点】
- 依赖注入:Set注入
- 依赖:bean对象的创建依赖容器
- 注入:bean对象中的所有属性,由容器来注入
【环境搭建】
-
复杂类型
public class Address { private String address; }
-
真实测试对象
public class Student { private String name; private Address address; private String[] books; private List<String> hobbies; private Map<String,String> card; private Set<String> game; private Properties info; private String wife; }
-
beans.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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="address" class="com.poppy.pojo.Address"> <property name="address" value="西安"/> </bean> <bean id="student" class="com.poppy.pojo.Student"> <!--基本类型--> <property name="name" value="Poppy"/> <!--复杂类型--> <property name="address" ref="address"/> <!--数组--> <property name="books"> <array> <value>语文</value> <value>数学</value> <value>英语</value> </array> </property> <!--列表--> <property name="hobbies"> <list> <value>听歌</value> <value>打游戏</value> <value>敲代码</value> </list> </property> <!--map类型--> <property name="card"> <map> <entry key="身份证" value="123456123412121234"/> <entry key="银行卡" value="123456123412121234"/> </map> </property> <!--set类型--> <property name="game"> <set> <value>LOL</value> <value>COC</value> <value>BOB</value> </set> </property> <!--Properties--> <property name="info"> <props> <prop key="学院">信息学院</prop> <prop key="班级">19计科</prop> </props> </property> <!--null--> <property name="wife"> <null/> </property> </bean> </beans>
-
测试
public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); Student student = context.getBean("student", Student.class); System.out.println(student); /* Student { name='Poppy', address=Address{address='西安'}, books=[语文, 数学, 英语], hobbies=[听歌, 打游戏, 敲代码], card={身份证=123456123412121234, 银行卡=123456123412121234}, game=[LOL, COC, BOB], info={班级=19计科, 学院=信息学院}, wife='null' } */ }
6.3、扩展方式注入
p命名空间注入
c命名空间注入
<?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">
<bean id="user" class="com.poppy.pojo.User" p:name="Poppy" p:age="18"/>
<bean id="user2" class="com.poppy.pojo.User" c:name="manjuas" c:age="18"/>
</beans>
测试类
@Test
public void test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("userBeans.xml");
User user = context.getBean("user", User.class);
System.out.println(user);
User user2 = context.getBean("user2", User.class);
System.out.println(user2);
}
注意点
-
p命名空间注入、c命名空间注入不能直接使用
-
需要加入xml约束!
-
xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c"
6.4、 Bean 作用域
-
单例模式【singleton】 Spring 默认机制
<bean id="user" class="com.poppy.pojo.User" p:name="Poppy" p:age="18" scope="singleton"/>
public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("userBeans.xml"); User user = context.getBean("user", User.class); User user2 = context.getBean("user", User.class); System.out.println(user2==user); // true }
-
原型模式【prototype】 每次从IOC【Spring】容器中getBean的时候 都会get一个新的对象!
<bean id="user" class="com.poppy.pojo.User" p:name="Poppy" p:age="18" scope="prototype"/>
public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("userBeans.xml"); User user = context.getBean("user", User.class); User user2 = context.getBean("user", User.class); System.out.println(user2==user); // false }
-
其余的Request、session、application、websocket这些只能在web开发中使用
7、自动装配
- 自动装配是Spring满足bean依赖的一种方式
- Spring会在上下文中自动寻找,并自动给bean自动装配属性
- 在Spring中有三种装配方式
- xml装配
- java装配【后边讲到】
- 隐式装配【重要!】
7.1、 环境
环境搭建:一个人拥有两个宠物!
POJO
public class People {
private Cat cat;
private Dog dog;
private String name;
}
public class Cat {
private void shout(){
System.out.println("miao!");
}
}
public class Dog {
private void shout(){
System.out.println("wang!");
}
}
7.2、 ByName自动装配
<!--
byName: 会自动在容器上下文中查找,和自己对象set方法后面的值对应的bean的id!
byName: 会自动在容器上下文中查找,和自己对象属性类型相同的bean!
-->
<bean id="people" class="com.poppy.pojo.People" autowire="byName">
<property name="name" value="Poppy"/>
</bean>
名字必须一致!!!大小写也不能错!【小写】
7.3、 ByType自动装配
一个class只能有一个bean! NoUniqueBeanDefinitionException
<!--
byName: 会自动在容器上下文中查找,和自己对象set方法后面的值对应的bean的id!
byName: 会自动在容器上下文中查找,和自己对象属性类型相同的bean!
-->
<bean id="people" class="com.poppy.pojo.People" autowire="byType">
<property name="name" value="Poppy"/>
</bean>
小结
- ByName需要保证bean的id唯一,且和这个类型的set后边的值相同!
- ByType需要保证bean的class唯一,并且和这个类型的注入的类型相同!
7.4、 使用注解实现自动注解
jdk1.5 支持注解, Spring2.5就支持注解!
The introduction of annotation-based configuration raised the question of whether this approach is “better” than XML.
使用注解须知:
-
导入约束!context约束
xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
-
配置注解的支持: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" xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" 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/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!--开启注解!--> <context:annotation-config/> </beans>
@Autowired 【Spring 的注解】
import org.springframework.beans.factory.annotation.Autowired;
- 直接在类中的属性上注解!可以不需要Set方法!
- 也可以在Set方法上边注解!
- 默认使用ByType方式查找 在使用ByName方式查找 如果两个都没法查找则报错!
- 使用Autowired我们可以不用编写Set方法了,前提是你这个自动装配的属性在IOC(Spring)容器中存在。
- @Qualifier配套使用!
- 当有多个bean对象(即一个class创建多个对象)的时候
- 不能通过名字查找的时候(id不是set后指定的名字) 可以用该注解指定名字查找
<bean id="cat3" class="com.poppy.pojo.Cat"/>
<bean id="cat2" class="com.poppy.pojo.Cat"/>
<bean id="dog" class="com.poppy.pojo.Dog"/>
<bean id="people" class="com.poppy.pojo.People">
<property name="name" value="Poppy"/>
</bean>
public class People {
@Autowired
@Qualifier(value = "cat2")
private Cat cat;
@Autowired
private Dog dog;
private String name;
}
科普:
@Nullable 字段标记了这个注解的时候 , 这个字段可以为空! 否则不行!
// 当我们显示的标注了@Autowired注解的required属性为false的时候 表明这个属性可以为空!
public @interface Autowired {
boolean required() default true;
}
@Resource【Java的注解】
import javax.annotation.Resource;
<bean id="cat3" class="com.poppy.pojo.Cat"/>
<bean id="cat2" class="com.poppy.pojo.Cat"/>
<bean id="dog" class="com.poppy.pojo.Dog"/>
<bean id="people" class="com.poppy.pojo.People">
<property name="name" value="Poppy"/>
</bean>
public class People {
@Resource(name = "cat2")
private Cat cat;
@Resource
private Dog dog;
private String name;
}
小结
@Resource和@Autowired的区别:
- 都是用来实现自动装配的,都可以放在属性和Set方法上
- @Autowired默认是使用ByType方式实现的,而且要求这个对象必须存在【常用】
- @Resource默认通过byName实现,如果找不到,则通过byType实现。如果两个都找不到的情况下,就报错!【常用】
- 执行顺序不同:@Autowired默认是使用ByType方式实现的 @Resource默认通过byName实现
8、使用注解开发
注意
在Spring4之后,要使用注解开发,必须要导入aop的包!
使用注解需要在xml中导入context的约束,添加注解的支持
-
bean
@Component
-
属性如何注入
// 相当于 : <bean id="user" class="com.poppy.pojo.User"/> // id : 就是这个类名的小写 // @Component : 组件 ----> 加了这个注解就意味着这个类交给Spring容器托管了! @Component public class User { // 给属性注入值! @Value("Poppy") private String name; }
-
衍生的注解
@Component有几个衍生的注解,在web项目中,按照MVC三层架构分层!
- dao【@Repository】
- service【@Service】
- controller【@Controller】
这四个注解作用都是相同! 都是代表将某个类注册到Spring容器中, 装配Bean
-
自动装配配置
@Autowired:自动装配通过类型,名字。 如果Autowired不能唯一自动装配上属性,则需要通过@Qualifier(value="xxx") @Nullable: 字段标记了这个注解,说明这个字段可以为空。 @Resource: 自动装配通过名字,类型。
-
作用域【@Scope】
@Scope(value = "singleton")
小结
- xml 更加万能!适用于所有情况下! 维护简单方便
- 注解 不是自己的类 不能使用 维护相对复杂
xml与注解最佳实践!
-
xml 只用来管理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: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>
9、使用Java的方式配置Spring
JavaConfig本来是Spring 的一个子项目, 在Spring4之后 就成为了它的核心【core】!
实体类
@Component
// 代表这个类已经注册到Spring容器中了
// 已经交给Spring托管了!
public class User {
// 实现属性的注入
@Value("Poppy")
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 这个类也会被注册到Spring容器中 交给Spring托管!
// 它本质就是一个Component
@Configuration
// 指定包下 注解生效!
// 在扫描的过程中 也会为Bean取一个默认id 返回类的小写!
// 所以在测试类中getBean的参数也可为这个id
@ComponentScan("com.poppy")
public class PoppyConfig {
@Bean
public User getUser(){
return new User();
}
}
测试类
public class MyTest {
@Test
public void test(){
ApplicationContext context = new AnnotationConfigApplicationContext(PoppyConfig.class);
User user = context.getBean("user", User.class);
System.out.println(user);
}
}
问题
- @ComponentScan(“com.poppy”) 扫描包的过程下,默认为id默认为User类的小写user
- 所以在测试类中! getBean()参数也可为类的小写!
10、代理模式
为什么学代理模式?因为这就是SpringAOP的底层!
代理模式的分类:
- 静态代理
- 动态代理
10.1、 静态代理
- 抽象角色 : 一般使用接口或者抽象类来实现【租房!】
- 真实角色 : 被代理的角色
- 代理角色 : 代理真实角色;代理真实角色后,一般会做一些附属操作!
- 客户 :使用代理角色来完成一些操作
-
接口
// 房东和代理都要实现的事情! public interface Rent { void rent(); }
-
真实角色
// 房东! public class Host implements Rent{ @Override public void rent() { System.out.println("房东要出租房子!"); } }
-
代理角色
package com.poppy.demo01; // 中介! public class Proxy implements Rent{ private Host host; public Proxy() { } public Proxy(Host host) { this.host = host; } @Override public void rent() { host.rent(); this.seeHouse(); this.draftContract(); this.charge(); } // 代理还会做一些附属操作! 不然只租房就毫无意义! public void seeHouse(){ System.out.println("中介带客户看房子!"); } // 收费! public void charge(){ System.out.println("中介收取中介费"); } // 拟定合同! public void draftContract(){ System.out.println("中介拟定合同"); } }
-
客户
// 客户 public class Client { public static void main(String[] args) { Host host = new Host(); Proxy proxy = new Proxy(host); proxy.rent(); } }
静态代理的好处:
- 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
- 公共的业务由代理来完成 . 实现了业务的分工 .
- 公共业务发生扩展时变得更加集中和方便 .
缺点 :
- 一个真实角色就会产生一个代理角色, 工作量变大了 . 开发效率会降低 .
我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理 !
10.2、 静态代理再理解!
-
接口
public interface UserService { void add(); void delete(); void update(); void query(); }
-
真实角色
package com.poppy.demo02; // 需求: 增加日志 // 在原有的代码基础上加日志! 改变了原有代码! // 增加一个代理! public class UserServiceImpl implements UserService{ @Override public void add() { System.out.println("增加了一个用户"); } @Override public void delete() { System.out.println("删除了一个用户"); } @Override public void update() { System.out.println("更新了一个用户"); } @Override public void query() { System.out.println("查询了一个用户"); } }
-
代理角色
package com.poppy.demo01; import com.poppy.demo02.UserService; import com.poppy.demo02.UserServiceImpl; public class UserServiceProxy implements UserService { private UserServiceImpl userService; // 在Spring思想中 尽量使用注入的方式! public void setUserService(UserServiceImpl userService) { this.userService = userService; } @Override public void add() { log("add"); userService.add(); } @Override public void delete() { log("delete"); userService.delete(); } @Override public void update() { log("update"); userService.update(); } @Override public void query() { log("query"); userService.query(); } public void log(String msg){ System.out.println("使用的"+msg+"方法"); } }
-
客户
package com.poppy.demo02; import com.poppy.demo01.UserServiceProxy; public class UserServiceController { public static void main(String[] args) { UserServiceImpl userService = new UserServiceImpl(); // 增加需求! // 再执行方法之前 需要加一个日志! //userService.add(); // 使用代理完成新的需求! UserServiceProxy proxy = new UserServiceProxy(); proxy.setUserService(userService); proxy.add(); } }
【开闭原则】Software entities like classes,modules and functions should be open for extension but closed for modifications.(一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。)
聊聊AOP
原则: 尽量不要修改原有的代码 在公司中是大忌!
我们在不改变原有代码的情况下,实现了对原有功能的增强,这是AOP中最核心的思想!
10.3、 动态代理
-
动态代理的角色和静态代理的一样
- 抽象对象 【一般使用接口或者抽象类实现!】
- 真实角色 【专注于做一件事–>租房】
- 要代理真实角色的代理角色 【一般会添加附属操作】
- 客户 【通过代理做一些操作】
-
动态代理的代理类是自动生成的,不是我们写好的;静态代理是我们提前写好的
-
动态代理一般分为两类:基于接口的动态代理,基于类的动态代理
- 基于接口的动态代理 :JDK动态代理 【我们在这里使用这个】
- 使用JDK原生代码实现,其余道理相同!
- InvocationHandler
- Proxy
- 基于类的动态代理 :cglib
- 现在用的比较多的是javassit来生成动态代理 【Java字节码】
- 基于接口的动态代理 :JDK动态代理 【我们在这里使用这个】
InvocationHandler
- InvocationHandler is the interface implemented by the invocation handler of a proxy instance.
- InvocationHandler 是由代理实例的调用处理程序实现的接口。
- invoke(Object proxy, Method method, Object[] args) —> 处理代理实例上的方法调用并返回结果。
Proxy
- newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
- 返回指定接口的代理类的实例,该接口将方法调用分派到指定的调用处理程序。
-
抽象类
// 房东和代理都要实现的事情! public interface Rent { 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); draftContract(); charge(); return result; } // 增加附属操作 // 看房子! public void seeHouse(){ System.out.println("中介带客户看房子!"); } // 收费! public void charge(){ System.out.println("中介收取中介费"); } // 拟定合同! public void draftContract(){ System.out.println("中介拟定合同"); } }
-
客户!
public class Client { public static void main(String[] args) { Host host = new Host(); // 我们现在没有代理对象 需要通过InvocationHandler接口(相当于一个工具类!) ProxyInvocationHandler pih = new ProxyInvocationHandler(); // 通过调用程序处理角色来处理我们要调用的接口对象 pih.setRent(host); // 获取代理对象! Rent proxy = (Rent) pih.getProxy(); proxy.rent(); } }
将其封装成一个工具类
public class ProxyInvocationHandle implements InvocationHandler {
// 要处理的一批业务!
private Object object;
// 通过调用程序处理角色来处理我们要调用的接口!(不太理解)
public void setObject(Object object) {
this.object = object;
}
// 获取代理对象 这是动态获取的 我们没有写代理类!
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
object.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(object, args);
return result;
}
}
动态代理的好处:
- 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
- 公共的业务由代理来完成 . 实现了业务的分工 .
- 公共业务发生扩展时变得更加集中和方便 .
- 一个动态代理 , 一般代理某一类业务.
- 一个动态代理可以代理多个类,代理的是接口!
11、AOP
11.1、 什么是AOP
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
11.2、 AOP在Spring中的作用
提供声明式事务;允许用户自定义切面
以下名词需要了解下:
- 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …
- 切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。
- 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
- 目标(Target):被通知对象。
- 代理(Proxy):向目标对象应用通知之后创建的对象。
- 切入点(PointCut):切面通知 执行的 **“地点”**的定义。
- 连接点(JointPoint):与切入点匹配的执行点。
在SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:
Aop即 在 不改变原有代码的情况下 , 去增加新的功能 .
11.3、 使用Spring实现AOP
方式一 【Spring API接口】
通过Srping API实现【主要是Spring API接口的实现】
在核心配置文件添加约束!
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
-
业务接口
public interface UserService { void add(); void delete(); void update(); void select(); }
-
实现类
public class UserServiceImpl implements UserService{ @Override public void add() { System.out.println("增加了一个用户"); } @Override public void delete() { System.out.println("删除了一个用户"); } @Override public void update() { System.out.println("更新了一个用户"); } @Override public void select() { System.out.println("查询了一个用户"); } }
-
日志类
-
AfterLog
public class AfterLog implements AfterReturningAdvice { /* returnValue:返回值 method:被调用的方法 args:参数 target:被调用的目标对象 即 拥有这个方法的类! */ @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("[AfterLog] "+target.getClass().getName() + "的" + method.getName() + "方法被调用了!"); } }
-
BeforeLog
public class BeforeLog implements MethodBeforeAdvice { /* method : 要执行的目标对象的方法 args : 被调用方法的参数 target : 目标对象 方法调用的目标。可能为空。 */ @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("[BeforeLog] "+target.getClass().getName() + "的" + method.getName() + "方法被调用了!"); } }
-
-
核心配置文件
<?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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!--注册 bean--> <bean id="userService" class="com.poppy.service.UserServiceImpl"/> <bean id="afterLog" class="com.poppy.log.AfterLog"/> <bean id="beforeLog" class="com.poppy.log.BeforeLog"/> <aop:config> <!-- 创建一个 切面! 即 在什么地方使用日志! expression execution(* * * * *) execution(修饰符 返回值 包名.类名/接口名.方法名(参数列表))注意老师忽略掉修饰符了 自己可以写上修饰符试试(..)可以代表所有参数,(*)代表一个参数, (*,String)代表第一个参数为任何值,第二个参数为String类型. --> <aop:pointcut id="pointcut" expression="execution(* com.poppy.service.UserServiceImpl.*(..))"/> <!--切入点 调用的方法!--> <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/> <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/> </aop:config> <!--使用aop之前 导入aop的约束!--> </beans>
-
测试
public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // 动态代理的是接口! UserService userService = context.getBean("userService", UserService.class); userService.add(); } }
方式二 【自定义类】
自定义类
public class DiyPointcut {
public void before(){
System.out.println("=======执行方法前=======");
}
public void after(){
System.out.println("=======执行方法后=======");
}
}
<bean id="diy" class="com.poppy.diy.DiyPointcut"/>
<!--方式二: 自定义类!-->
<aop:config>
<!--自定义切面 ref 为引用的类型!-->
<aop:aspect ref="diy">
<aop:pointcut id="point" expression="execution(* com.poppy.service.UserServiceImpl.*(..))"/>
<aop:after method="after" pointcut-ref="point"/>
<aop:before method="before" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
方式三 【注解】
开启注解支持
<aop:aspectj-autoproxy/>
@Aspect// 给当前类设置切面!
public class AnnotationPointcut {
// 给当前类设置通知
@Before("execution(* com.poppy.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("======方法执行前=======");
}
@After("execution(* com.poppy.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("======方法执行后=======");
}
@Around("execution(* com.poppy.service.UserServiceImpl.*(..))")
public void round(ProceedingJoinPoint pj) throws Throwable {
System.out.println("======环绕前=======");
// 签名!
System.out.println(pj.getSignature());
// 执行方法
// 执行的过程中包含了after() before()
Object proceed = pj.proceed();
System.out.println("======环绕后=======");
}
}
12、Spring 整合 Mybatis
官方文档
http://mybatis.org/spring/zh/index.html
- 导包!
- junit
- mybatis
- mysql
- aop-织入 aspectjweaver
- spring-web
- spring-jdbc
- mybatis-spring
<dependencies> <!--junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13</version> </dependency> <!--mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!--spring--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.12</version> </dependency> <!--aspectj--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.7</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.12</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.6</version> </dependency> </dependencies>
- 编写配置文件
- 测试
12.1、 回顾Mybatis
-
编写实体类
public class User { private int id; private String name; private String pwd; }
-
编写实体类对应的mapper
public interface UserMapper { List<User> getUsers(); }
-
写mapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.poppy.mapper.UserMapper"> <select id="getUsers" resultType="User"> select * from mybatis.user; </select> </mapper>
-
测试
public void test() throws IOException { String resource = "mybatis-config.xml"; InputStream in = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> users = mapper.getUsers(); for (User user : users) { System.out.println(user); } }
12.2、 Spring-Mybatis
方式一 【sqlSessionTemplate】
-
实体类
-
Mapper【接口】
public interface UserMapper { List<User> getUsers(); }
-
mybatis配置文件 【别名+设置】
<?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="com.poppy.pojo"/> </typeAliases> </configuration>
-
spring-dao配置文件 【单纯配置一些设置 和 mybatis中的配置】
- 数据源
- sqlSessionFactory
- sqlSession
<?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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!--数据源 现在我们使用spring的--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="304430"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--绑定数据源--> <property name="dataSource" ref="dataSource"/> <!--绑定Mybatis配置文件--> <property name="configLocation" value="classpath:mybatis-config.xml"/> <property name="mapperLocations" value="classpath:com/poppy/mapper/*.xml"/> </bean> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <!-- 均为有参构造! 且无set方法 所以只能使用构造器注入属性!--> <constructor-arg index="0" ref="sqlSessionFactory"/> </bean> </beans>
-
整合到applicationContext.xml配置文件中
- 导入spring-dao 等配置
- 注册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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <import resource="spring-dao.xml"/> <bean id="userMapper" class="com.poppy.mapper.UserMapperImpl"> <property name="sqlSession" ref="sqlSession"/> </bean> </beans>
-
接口实现类
因为sqlSessionFactory sqlSession交付Spring托管! 所以通过实现类 将sqlSession注入!
public class UserMapperImpl implements UserMapper{ private SqlSessionTemplate sqlSession; public void setSqlSession(SqlSessionTemplate sqlSession) { this.sqlSession = sqlSession; } @Override public List<User> getUsers() { UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> users = mapper.getUsers(); return users; } }
-
测试
public void test() throws IOException { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserMapperImpl userMapper = context.getBean("userMapper", UserMapperImpl.class); List<User> users = userMapper.getUsers(); for (User user : users) { System.out.println(user); } }
方式二 【SqlSessionDaoSupport】
继承SqlSessionDaoSupport这个类,为你直接提供一个getSqlSession的方法
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{
@Override
public List<User> getUsers() {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
List<User> users = mapper.getUsers();
return users;
}
}
<bean id="userMapper2" class="com.poppy.mapper.UserMapperImpl2">
<!--SqlSessionDaoSupport 需要一个sqlSessionFactory-->
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
区别:
- 注入属性不同
13、声明式事务
13.1、 事务回顾
- 把一组业务当成一个业务来做;要么都成功,要么都失败!
- 事务在项目开发中,十分的重要,涉及到数据的一致性和完整性问题,不能马虎!
- 确保完整性和一致性!
事务ACID原则:
- 原子性
- 一致性
- 隔离性
- 多个业务可能操作同一个资源,防止数据损坏!
- 持久性
- 事务一旦提交,无论系统发生什么问题,结果都不会再被影响,被持久的写到存储器中!
Spring中的事务管理
- 声明式事务:AOP的应用
- 编程式事务:需要在代码中,进行事务的管理【改源码】
13.2、 Spring中事务隔离级别和传播行为
【Spring中事务隔离级别】
隔离级别 | 含义 |
---|---|
ISOLATION_DEFAULT | 使用后端数据库默认的隔离级别。 |
ISOLATION_READ_UNCOMMITTED | 允许读取尚未提交的更改。可能导致脏读、幻影读或不可重复读。 |
ISOLATION_READ_COMMITTED | 允许从已经提交的并发事务读取。可防止脏读,但幻影读和不可重复读仍可能会发生。 |
ISOLATION_REPEATABLE_READ | 对相同字段的多次读取的结果是一致的,除非数据被当前事务本身改变。可防止脏读和不可重复读,但幻影读仍可能发生。 |
ISOLATION_SERIALIZABLE | 完全服从ACID的隔离级别,确保不发生脏读、不可重复读和幻影读。这在所有隔离级别中也是最慢的,因为它通常是通过完全锁定当前事务所涉及的数据表来完成的。 |
【Spring中事务传播行为】
REQUIRED | 业务方法需要在一个事务中运行,如果方法运行时,已处在一个事务中,那么就加入该事务,否则自己创建一个新的事务.这是spring默认的传播行为. |
---|---|
SUPPORTS | 如果业务方法在某个事务范围内被调用,则方法成为该事务的一部分,如果业务方法在事务范围外被调用,则方法在没有事务的环境下执行. |
MANDATORY | 只能在一个已存在事务中执行,业务方法不能发起自己的事务,如果业务方法在没有事务的环境下调用,就抛异常. |
REQUIRES_NEW | 业务方法总是会为自己发起一个新的事务,如果方法已运行在一个事务中,则原有事务被挂起,新的事务被创建,直到方法结束,新事务才结束,原先的事务才会恢复执行. |
NOT_SUPPORTED | 声明方法需要事务,如果方法没有关联到一个事务,容器不会为它开启事务.如果方法在一个事务中被调用,该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行. |
NEVER | 声明方法绝对不能在事务范围内执行,如果方法在某个事务范围内执行,容器就抛异常.只有没关联到事务,才正常执行. |
NESTED | 如果一个活动的事务存在,则运行在一个嵌套的事务中.如果没有活动的事务,则按REQUIRED属性执行.它使用了一个单独的事务, 这个事务拥有多个可以回滚的保证点.内部事务回滚不会对外部事务造成影响, 它只对DataSourceTransactionManager 事务管理器起效. |
13.3、 声明式事务
增加业务 修改接口
public interface UserMapper {
List<User> getUsers();
int addUser(User user);
int deleteUser(@Param("id") int id);
}
实现类
public class UserMapperImpl implements UserMapper{
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public List<User> getUsers() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
addUser(new User(5,"manjuas","456651"));
deleteUser(5);
List<User> users = mapper.getUsers();
return users;
}
@Override
public int addUser(User user) {
return sqlSession.getMapper(UserMapper.class).
addUser(user);
}
@Override
public int deleteUser(int id) {
return sqlSession.getMapper(UserMapper.class).deleteUser(id);
}
}
mapper.xml 【满足测试 故意写错删除语句】
<mapper namespace="com.poppy.mapper.UserMapper">
<select id="getUsers" resultType="User">
select * from mybatis.user;
</select>
<insert id="addUser" parameterType="user">
insert into mybatis.user(id, name, pwd) VALUES (#{id},#{name},#{pwd})
</insert>
<delete id="deleteUser" parameterType="_int">
deletes
from mybatis.user
where id = #{id};
</delete>
</mapper>
测试
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
for (User user : userMapper.getUsers()) {
System.out.println(user);
}
}
问题
- delete语句错误 也插入进去了一条记录!
- 不符合ACID原则!
完善!
在spring-dao.xml文件中配置声明式事务 通过aop实现事务的切入!
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://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
https://www.springframework.org/schema/tx/spring-tx.xsd">
<!--数据源 现在我们使用spring的-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="304430"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--绑定数据源-->
<property name="dataSource" ref="dataSource"/>
<!--绑定Mybatis配置文件-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/poppy/mapper/*.xml"/>
</bean>
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!-- 均为有参构造! 且无set方法 所以只能使用构造器注入属性!-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
<!--配置声明式事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--
结合AOP实现事务的织入
配置事务的通知!
配置事务的传播性! propagation
-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--REQUIRED 默认-->
<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="update" propagation="REQUIRED"/>
<tx:method name="delete" propagation="REQUIRED"/>
<tx:method name="query" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--配置事务切入!-->
<aop:config>
<aop:pointcut id="txPointcut" expression="execution(* com.poppy.mapper.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
</beans>
不需要改变其他代码!
思考:
为什么需要事务?
- 如果不配置事务,可能存在数据提交不一致的情况;
- 如果我们不在Spring中配置声明式事务,就需要在代码中手动配置事务!
- 事务在项目开发中,十分的重要,涉及到数据的一致性和完整性问题,不能马虎!