课代表笔记(本篇文章的重点):
-
本篇文章从代理模式出发,介绍静态代理模式和动态代理模式,了解AOP的底层的实现
接下来详细介绍AOP思想
和Spring中AOP的开发流程
-
Spring框架和MyBatis框架整合
,让开发的效率更高
两者都是企业面试笔试最常问到的知识点,或者换一句话说,这是企业招人最低的要求
-
最后讲Spring的
注解开发
,省去了配置实体类和注入实体类烦杂的流程 -
Spring集成JUnit,
让测试写更少的代码
前文推荐
:
Spring基础入门到Spring IOC介绍(本篇文章竟然将这么多Spring的底层实现,面试官又得问了)
在讲SpringAOP之前,先介绍一下代理设计模式,能更好的理解AOP的思想和AOP的底层实现
代理设计模式
概念
将核心功能与辅助功能(事务.日志.性能监控代码)分离,达到核心业务功能更纯粹.辅助业务可复用
功能分离 |
---|
静态代理模式
通过代理类的对象,为原始类的对象(目标类的对象添加辅助功能),更容易更换代理实现类,利于维护
静态代理 |
---|
结构 |
---|
- 代理类和原始类中要保证实现功能一致,因此让他们实现同一接口
接口
package per.leiyu.entiry;
/** * @author 雷雨 * @date 2020/6/19 21:15 */
public interface FangdongService {
public void zufang();
}
房东
package per.leiyu.entiry;
/** * @author 雷雨 * @date 2020/6/19 21:15 */
public class FangdongServiceImpl implements FangdongService{
@Override
public void zufang() {
//核心功能
System.out.println("签合同");
System.out.println("收房租");
}
}
代理
package per.leiyu.entiry;
/** * @author 雷雨 * @date 2020/6/19 21:17 */
public class FangdongServiceProxy implements FangdongService {
private FangdongService fangdongService = new FangdongServiceImpl();
@Override
public void zufang() {
//辅助功能呢
System.out.println("发布租房信息");
System.out.println("带租客看房");
//核心功能
//核心功能仍然由原始类完成
fangdongService.zufang();
}
}
静态代理的特点
- 的确解决了原始业务类中核心功能和辅助功能的耦合
- 但是在
代理类中又出现了核心功能和辅助功能的耦合
因为在静态代理中我们明确的创建了代理对象,那么就增加了代理类的维护成本,我们想要的是既能将辅助功能分离,还不需要我们自己创建代理类对象,那么就使用动态代理.在程序运行过程中,通过反射机制,动态的为我们生成一个类来解决原始类中的问题
动态代理设计模式
动态创建代理类的对象,为原始类的对象添加辅助功能
JDK动态代理实现(基于接口)
package per.leiyu.factoryTest;
import org.junit.Test;
import per.leiyu.entiry.FangdongService;
import per.leiyu.entiry.FangdongServiceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/** * @author 雷雨 * @date 2020/6/19 21:38 */
public class TestProxyByjdk {
@Test
public void test1(){
//目标
FangdongService fangdongService = new FangdongServiceImpl();
//额外功能
InvocationHandler hi= new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//辅助功能呢
System.out.println("发布租房信息");
System.out.println("带租客看房");
//核心功能
//核心功能仍然由原始类完成
fangdongService.zufang();
return null;
}
};
//动态生成代理类
//因为这个代理是目标接口的实现类(和静态代理一样,要保证目标类和代理的功能统一),因此用接口接收代理实现类没有问题
FangdongService proxy = (FangdongService)Proxy.newProxyInstance(TestProxyByjdk.class.getClassLoader(), fangdongService.getClass().getInterfaces(), hi);
proxy.zufang();
}
}
动态代理-基于接口 |
---|
CGLIB动态代理
@Test
public void test2(){
//目标
FangdongService fangdongService = new FangdongServiceImpl();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(FangdongServiceImpl.class);
enhancer.setCallback(new org.springframework.cglib.proxy.InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
//辅助功能呢
System.out.println("发布租房信息");
System.out.println("带租客看房");
//核心功能
//核心功能仍然由原始类完成
fangdongService.zufang();
return null;
}
});
//动态创建代理类
FangdongServiceImpl proxy = (FangdongServiceImpl)enhancer.create();
proxy.zufang();
}
动态代理-基于继承 |
---|
- 也就是把目标类当做了父类,其子类肯定继承了父类的核心功能,子类再实现一些额外的功能
面向切面编程
前面讲到动态代理模式的确能够把类之间的耦合去掉,但是为每一个类都编写这么大量的代码费事费力,在Spring中封装了动态代理的功能,并为我们提供了友好的方法.
概念
AOP(Aspect Oriented Programming),即面向切面编程,利用一种称为"横切"的技术,刨开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面,简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性
AOP开发术语
- 连接点(Joinpoint):连接点是程序类中客观存在的方法,可被Spring拦截并切入内容
- 切入点(Ponintcut):被Spring切入连接点
- 通知.增强(Advice):可以为切入点添加额外的功能,分为前置通知.后置通知.异常通知.环绕通知等
- 目标对象(Target):代理的目标对象
- 引介(Introduction):一种特殊的增强,可在运行期为类动态添加Field和Method
- 织入(Weaving):把通知应用到具体的类,进而创建新的代理类的过程
- 代理(Proxy):被AOP织入通知后,产生的结果类
- 切面(Aspect):由切点和通知组成,将横切逻辑织入切面所指定的连接点中
作用
Spring的AOP编程既是通过动态代理类为原始类的方法添加辅助功能
环境搭建
引入AOP相关依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
开发流程
定义原始类
package per.leiyu.service.userServiceImpl;
import per.leiyu.service.Userservice;
import java.util.List;
/** * @author 雷雨 * @date 2020/6/20 8:31 */
public class UserserviceImpl implements Userservice {
@Override
public Integer queryUserByid() {
System.out.println("核心功能" +"查询用户通过id值");
return 1;
}
@Override
public void queryUserByName(String name) {
System.out.println("核心功能查询用户通过用户名");
}
@Override
public List queryUserByNameAndPassword(String name, String password) {
System.out.println("核心功能查询用户通过用户名和密码");
return null;
}
}
package per.leiyu.service;
import java.util.List;
/** * @author 雷雨 * @date 2020/6/20 8:29 */
public interface Userservice {
Integer queryUserByid();
public void queryUserByName(String name);
List queryUserByNameAndPassword(String name, String password);
}
定义通知类(实现MethodxxxAdvice)
package per.leiyu;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
/** * @author 雷雨 * @date 2020/6/20 8:34 */
public class myAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("辅助功能1");
System.out.println("辅助功能2");
}
}
定义bean标签
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" 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-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userService" class="per.leiyu.service.userServiceImpl.UserserviceImpl"></bean>
<bean id="before" class="per.leiyu.myAdvice"></bean>
<!--编织 -->
<aop:config>
<!--定义切入点 修饰符 返回值 包.类 方法名 参数表-->
<aop:pointcut id="pc_leiyu" expression="execution(* queryUserByid())"/>
<!-- 组装 advice-ref:通知 pointcut-ref:切入点 -->
<aop:advisor advice-ref="before" pointcut-ref="pc_leiyu"/>
</aop:config>
</beans>
- 注意配置头中添加了aop的配置
定义切入点
package service;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import per.leiyu.service.Userservice;
/** * @author 雷雨 * @date 2020/6/20 8:36 */
public class MyserviceAdvice {
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
Userservice proxy = (Userservice)context.getBean("userService");
System.out.println(proxy.getClass());
proxy.queryUserByid();
}
}
运行结果
如果运行出来有辅助功能说明我们的动态代理实现成功了
运行结果 |
---|
- 注意:通过Spring AOP实现的动态代理获得的代理类可以用原始类接口接收(原因和JDK实现动态代理的原因相同)
AOP中的各种通知类
定义通知类,达到通知的效果
- 前置通知:MethodBeforeAdvice
- 后置通知:AfterAdvice
- 后置通知:AfterReturningAdvice 有异常不执行,方***因异常而结束,无返回值
- 异常通知:ThrowsAdvice
- 环绕通知:MethodInterceptor
切入点表达式
根据表达式通配切入点
修饰符 返回值 包.类 方法名 参数列表
<!-- 匹配参数-->
<aop:pointcut id="myPointCut" expression="execution(* *(per.leiyu.service.UserserviceImpl))"/>
<!--匹配方法名(无参) -->
<aop:pointcut id="myPointCut" expression="execution(* save())"/>
<!--匹配方法名(任意参数)-->
<aop:pointcut id="myPointCut" expression="execution(* save(..))"/>
<!--匹配返回值类型 -->
<aop:pointcut id="myPointCut" expression="execution(per.leiyu.service.UserServiceImpl *(..))"/>
<!-- 匹配类名 -->
<aop:pointcut id="myPointCut" expression="execution(* per.leiyu.service.*(..))"/>
<!-- 匹配包名 -->
<aop:pointcut id="myPointCut" expression="execution(* perleiyu.*.*(..))"/>
<!-- 匹配包名以及子包名-->
<aop:pointcut id="myPointCut" expression="execution(* per.leiyu..*.*(..))"/>
Spring AOP中使用JDK和CGlib选择
在Spring中实现动态代理的功能来解耦合,可以使用两种方式JDK的方法和CGlib的方式
那么Spring底层是如何选择代理模式的?
基本规则:目标业务类如果有接口则用JDK代理,没有接口则使用CGlib代理
看看java中的源码 |
---|
后处理器
Spring中定义了很多后处理器
每个bean在创建完成之前,都会有一个后处理过程,即再加工,对bean做出相应的改变和调整.
Spring-AOP中,就有一个专门的后处理器,负责通过原始业务组件(Service),再加工得到一个代理组件
自定义一个后处理器
自定义后处理器了解后处理器的运行
实体类
package per.leiyu.entity;
import javax.print.attribute.standard.PrinterURI;
/** * @author 雷雨 * @date 2020/6/20 12:04 */
public class User {
private Integer id;
public User() {
System.out.println("User的构造方法");
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
System.out.println("User的set");
}
public void initUser(){
System.out.println("初始化方法");
}
}
后处理类
需要继承BeanPostProcessor接口
package per.leiyu.entity;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
/** * @author 雷雨 * @date 2020/6/20 12:03 * * 定义后处理器: * 作用在bean创建之后,对bean进行再加工 */
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object o, String s) throws BeansException {
System.out.println("后处理器1");
System.out.println("后处理器1"+o+s);
return null;
}
@Override
public Object postProcessAfterInitialization(Object o, String s) throws BeansException {
System.out.println("后处理器2");
System.out.println("后处理器2"+o+s);
return null;
}
}
Spring配置
<!-- 后处理器-->
<bean id="user1" class="per.leiyu.entity.User" init-method="initUser">
<property name="id" value="1"></property>
</bean>
<bean id="beanPostProcessor" class="per.leiyu.entity.MyBeanPostProcessor"></bean>
测试类
package service;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/** * @author 雷雨 * @date 2020/6/20 12:08 */
public class TestMyBeanPostProcessor {
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
}
}
结果 |
---|
在Spring中集成了很多后处理器,不需要我们自己来编写代码
常用处理器 |
---|
由此可得:我们的动态代理是在
目标Bean的后处理过程中生成的
完成的Bean声明周期
构造
>>注入属性 满足依赖
>>后处理器前置过程
>>初始化
>>后处理器后置过程
>>返回
>>销毁
Spring和MyBatis整合
配置数据源
将数据源配置到项目中
引入jdbc.properties配置文件
#jdbc.properties
jdbc.driver =com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true
jdbc.username=root
jdbc.passward=123456
#jdbc.xxx的jdbc是标识名,可以修改,是为了我们方便记忆和书写,你如果愿意,也可以修改为aaa.xxx
整合Spring配置文件和properties配置文件
创建一个Spring的配置文件,然后把MyBatis的配置可以直接写入,这样就不用再单独给MyBatis建立配置文件
<?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 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!--使用$占位符索引到jdbc.properties配置文件中的具体内容-->
<property name="driver" value="${jdbc.driver}" />
<!-- 转义字符& 方法在下面标签中有具体演示-->
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.passward}" />
</bean>
</beans>
Druid监控中心
<!-- web.xml-->
<servlet>
<servlet-name>DruidStatView</servlet-name>
<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DruidStatView</servlet-name>
<url-pattern>/druid/*</url-pattern>
</servlet-mapping>
测试监控中心
配置tomcat,并访问protocol://ip:port/project/druid/index.html
整合MyBatis
将SqlSessionFactory.DAO.Service配置到项目中
导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<!-- MyBatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.3.0</version>
</dependency>
<!--Spring整合MyBatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.2.3</version>
</dependency>
<!-- Mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
配置SqlSessionFactory
首先这个是一个FactoryBean,而FactoryBean是用来创建复杂对象的,那么SqlSessionFactory就是为了创建SqlSessionFactory的复杂的对象
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注入连接池-->
<property name="dataSource" ref="dataSource"></property>
<property name="mapperLocations">
<list>
<value>classpath:per/leiyu/dao/*.xml</value>
</list>
</property>
<!-- 为 dao-mapper文件中的实体 定义缺省包路径-->
<!-- 如:<select id="queryAll" resultType="User"> 中User类可以不定义包-->
<property name="typeAliasesPackage" value="per.leiyu.entity"></property>
</bean>
创建一个UserDao
为了测试我们的MyBatis的整合
package per.leiyu.dao;
import per.leiyu.entity.User;
import java.util.List;
/** * @author 雷雨 * @date 2020/6/20 16:54 */
public interface UserDao {
List<User> queryUsers();
}
创建实体类
package per.leiyu.entity;
import java.util.Date;
/** * @author 雷雨 * @date 2020/6/20 16:55 */
public class User {
private Integer id;
private String username;
private String passward;
private Boolean gender;
private Date registTime;
public User() {
super();
}
public User(Integer id, String username, String passward, Boolean gender, Date registTime) {
this.id = id;
this.username = username;
this.passward = passward;
this.gender = gender;
this.registTime = registTime;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", passward='" + passward + '\'' +
", gender=" + gender +
", registTime=" + registTime +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassward() {
return passward;
}
public void setPassward(String passward) {
this.passward = passward;
}
public Boolean getGender() {
return gender;
}
public void setGender(Boolean gender) {
this.gender = gender;
}
public Date getRegistTime() {
return registTime;
}
public void setRegistTime(Date registTime) {
this.registTime = registTime;
}
}
配置对象关系映射
这里是基于UserDao接口
写sql语句
这里的配置方式和MyBatis中完全相同
<?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">
<!-- 指明该xml文件时用来描述那个dao接口的-->
<mapper namespace="per.leiyu.dao.UserDao">
<select id="queryUsers" resultType="User">
select id,username,passward,gender,regist_time
from t_user
</select>
</mapper>
创建测试类
package per.leiyu;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import per.leiyu.dao.UserDao;
import per.leiyu.entity.User;
import java.util.List;
/** * @author 雷雨 * @date 2020/6/20 17:08 */
public class TestSpringMyBatis {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
SqlSessionFactory sqlSessionFactory =(SqlSessionFactory) context.getBean("sqlSessionFactory");
SqlSession sqlSession = sqlSessionFactory.openSession();
UserDao mapper = sqlSession.getMapper(UserDao.class);
List<User> users = mapper.queryUsers();
for (User user : users) {
System.out.println(user);
}
}
}
成功读取到了数据库中数据 |
---|
给出文件结构 |
---|
配置MapperScannerConfigurer
管理DAO实现类的创建,并创建DAO对象,存入工厂管理
- 扫描所有DAO接口,去构建DAO实现
- 将DAO实现存入工厂管理
- DAO实现对象在工厂中的id是:
首字母小写的-接口的类名
,例如:UserDAO==>userDAO,OrderDAO==>orderDAO
- 意思就是如果你的Dao接口是UserDAO,那么它在工厂中的bean对象就是userDAO,虽然这个bean我们看不到,但是可以正常的使用,是Spring动态为我们创建的
<!-- mapperScannerConfigurer-->
<bean id="mapperScannerConfigurer2" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--dao接口所在的包,如果有多个包,可以用逗号或分号分隔 <property name="basePackage" value="dao接口所在的包,多个包用逗号分隔"></property> -->
<property name="basePackage" value="per.leiyu.dao"></property>
<!-- 如果工厂中只有一个SqlSessionFactory的bean,此配置可省略-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
测试Spirng为我们动态的生成了Dao接口的实现类对象
那么我们能够使用这个动态创建出来的对象调用Dao接口的方法就代表Spirng为我们动态的生成了Dao接口的实现类对象
@Test
public void testMapperScannerConfigurer(){
ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
//这个bean对象在Spring的配置文件中没有注册,这是我们根据规则推测出来的
UserDao userDao=(UserDao) context.getBean("userDao");
List<User> users = userDao.queryUsers();
for (User user : users) {
System.out.println(user);
}
}
也能正常的打印我们查询的结果 |
---|
配置service
创建接口和实现类
都是基于我们之前的UserDao(我们做的就是关于UserDao的service实现)
package per.leiyu.service;
import per.leiyu.entity.User;
import java.util.List;
/** * @author 雷雨 * @date 2020/6/20 20:49 */
public interface UserService {
public List<User> queryUser();
}
package per.leiyu.service;
import per.leiyu.dao.UserDao;
import per.leiyu.entity.User;
import java.util.List;
/** * * @author 雷雨 * @date 2020/6/20 20:50 */
public class UserServiceImpl implements UserService {
//这里需要用到UserDao,我们使用了Spring IOC的思想
private UserDao userDao;
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public List<User> queryUser() {
return userDao.queryUsers();
}
}
将service注册进Spring
<!-- UserService-->
<bean id="userService" class="per.leiyu.service.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
创建测试
@Test
public void testUserService(){
ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
UserService userService =(UserService) context.getBean("userService");
List<User> users = userService.queryUser();
for (User user : users) {
System.out.println(user);
}
}
service成功打印数据库数据 |
---|
事务
配置DataSourceTransactionManager
事务管理器,其中持有DataSource,可以控制事务功能(commit,rollback)等
<!--DataSourceTransactionManager -->
<!-- 引入一个事务管理器,其中依赖DataSource,借以获得连接,进而控制事务逻辑-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
- 注意:DataSourceTransactionManager和SqlSessionFactoryBean要注入同一个DataSource的Bean,否则事务控制失败
配置事务通知
基于事务管理器,进一步定制,生成一个额外功能:Advice
此Advice可以切入任何需要事务的方法,通过事务管理器为方法控制事务.
我们之前讲过额外功能,而事务就是不属于我们业务的额外功能,而且事务有一个特点:
在核心功能之前开启,在核心功能执行之后提交
----因此是一个环绕通知
Spring中为我们定制了管理事务的通知的标签
- 为了不同方法都写一个事务属性,这样做的好处是,当执行不同的方法时,可以切换到不同的事务属性执行-----控制事务更加精密
事务属性
1.隔离级别
isolation
隔离级别当多事务并发时,事务之间是有隔离级别的,隔离级别越高,说明共享的数据越少,并发的效率就越低,但是对应着安全性就越高
隔离级别 | 解释 |
---|---|
default | (默认值)(采用数据库的默认的设置)(建议) |
read-uncommited | 读未提交 |
read-commited | 读提交(Oracle数据库默认的隔离级别) |
repeatable-read | 可重复度(Mysql数据库默认的隔离级别) |
serialized-read | 序列化读 |
隔离级别由低到高:
read-uncommited
–<read-commited
–<repeatable-read
–<serialized-read
- 安全性:级别越高,多事务并发时,越安全.因为共享的数据越来越少,事务间批次干扰减少
- 并发性:级别越高,多事务并发时,并发越差.因为共享的数据越来越少,事务间阻塞情况增多
事务并发时的安全问题:
- 脏读:读脏数据,一个事务读取到另一个事务还未提交的数据
- 大于等于read-commited 可防止
- 不可重复读:一个事务内多次读取一行数据的相同内容,其结果不一致(主要针对更新中发生的,事务A读取一行数据,事务B也读取这行数据并更新了数据,事务A重新读取这行数据的时候发现这一行内容前后不一致)
- 大于等于 repeatable-read可防止
- 幻影读:一个书屋内多次读取一张表中的相同内容,其结果不一致(主要指的是行数不一致)(主要针对的是删除和添加中发生的,事务A读取这个表的数据,事务B也读取这个表数据并删除(或者添加)了部分数据,事务A重新读取这个表数据的时候发现这一格表的内容(行数都不相同)前后不一致)
- serialized-read 可防止
2.传播行为
propagation
传播行为
事务嵌套:事务A(Service A)中要调用事务B(Service B),那么如果事务B发生了错误会回滚,但是由于原子的隔离性(事务A和事务B不属于同一个原子),那么可能事务A的操作就没能得到回滚
假如事务A是一个查询操作,本身不存在对数据的操作,那么不会发生错误
假如事务A是一个增删改的操作,那么A不回滚,就会造成错误
所以我们要讨论事务之间的传播性
当涉及到事务嵌套(Service调用Service)中,可能会出现问题
- SUPPORTS = 不存在外部事务,则不开启新事物;存在外部事务,则合并到外部事务中.
适合查询
- REQUIRD = 不存在外部事务,则开启新事务;存在外部事务,则合并到外部事务中.(默认值)
适合增删改
3.读写性
readonly
读写性
- true:只读,可提高查询效率(适合查询)
- false:可读可写.(默认值)(适合增删改)
4.事务超时
timeout
:事务超时时间
当前事务所需操作的数据被其他事务占用,则等待.
- 100:自定义等待时间为100(秒)
- -1:由数据指定等待时间,默认值.(建议)
5.事务回滚
roolback
回滚属性
- 如果事务中抛出RuntimeException,则自动回滚
- 如果事务中抛出CheckException(非运行时异常Exception),不会自动回滚,而是默认提交事务
- 处理方案:将CheckException转换成RuntimeException上抛,或设置rollback-for=“exception”
编织
将刚才配置为事务通知编织到我们我们的Service的实现类中
注解开发
声明bean
用于替换自建类型组件的<bean…>标签;可以更快速的声明bean
@Service
业务类专用
@Repository
dao实现类专用
@Controller
web层专用
@Component
通用
@Scope
用户控制bean的创建模式(默认为单例创建模式,可以根据需要设置为多例的创建模式)
//@Service说明 此类是一个业务类,需要将此类纳入工厂,等价替换掉<bean class="xxx.UserServiceImpl">
//@Service默认beanId= 首字母小写的类名"userServiceImpl"
//@Service("userService") 自定义beanId为userService
@Service//声明bean,且id是userServiceImpl
@Scope("singleton")//声明创建模式,默认为单例模式;@Scope("prototype")即可设置为多例模式
public class UserServiceImpl implements UserService{
...
}
注入
用于完成bean中属性值的注入
@Autowired
基于类型自动注入
@Resource
基于名称自动注入
@Qualifier("userDao")
限定要自动注入的bean的id,一般和@Autowired联用
@Value
注入简单类型数据(jdk8种+String)
@Service
public class UserService implements UserService{
@Autowired //注入类型为UserDAO的bean
@Qualifier("userDAO2")//如果有多个类型为UserDAO的bean,可以用此注解在其中选在其中一个
private UserDAO userDAO;
@Value("leiyu")//注入String
private String name;
...
}
- 因为仅仅通过类型注入的话,可以会有多个类型都相同,但是注入的bean只能是一个,所以需要再控制bean的id
事务控制
用于控制事务切入
@Transactional
工厂配置中的<tx:advice…和<aop:config…可以省略
//类中的每个方法都切入事务(有自己的事务控制的方法除外)
@Transactional(isolation=Isolation.READ_COMMITED,proagation=Propagation.REQUIRED,readonly=false,rollbackFor=Exception.class,timeout=-1)
public class UserServiceImpl implements UserService{
...
//该方法自己的事务控制,仅对该方法有效
//如果即在类上加了事务控制,又在方法上加了事务控制,那么以方法上的为准
@Transactional(propagation=Propagation.SUPPORTS)
public List<User> queryAll(){
return userDao.queryAll();
}
public void save(User user){
userDao.save(user);
}
}
注解所需的配置
<!-- 告知Spring,那些包中 有被注解的类.方法 属性-->
<!-- 只有告知了Spring被注解的地方,Spring才能去找到这些类并去帮我们创建对象 -->
<!-- <context:component-scan base-package="per.leiyu"></context:component> -->
<context:component-scan base-package="per.leiyu"></context:component>
<!--告知Spring,@Transactional在定制事务时,是基于txManager=DataSourceTransactionManager -->
<!-- 事务的管理是需要我们的事务管理器的,事务管理器的功能在上面有说明 -->
<tx:annotation-driven transaction-manager="txManager">
AOP开发
注解使用
package per.leiyu;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Properties;
/** * @author 雷雨 * @date 2020/6/21 16:08 */
@Aspect //声明此类是一个切面类:会包含切入点(pointcut)和通知(advice)
@Component //声明组件 进入工厂
public class MyAspect {
//定义切入点
// 这里为了定义切入点,因为基于声明的Spring中切入点定义是有一个id值的,我们这的方法名就相当于id值
@Pointcut("execution(* per.leiyu.service.UserServiceImpl.*(..))")
public void pc(){}
@Before("pc()")
public void mybefore(JoinPoint a){
System.out.println("target"+a.getTarget()); //获取当前的目标是那个(方法)
System.out.println("args:"+a.getArgs()); //获取当前目标方法的参数
System.out.println("method's name"+a.getSignature().getName()); //当前调用这个方法的名称是
System.out.println("before");
}
@AfterReturning(value="pc()",returning = "ret")//后置通知
public void myAfterReturning(JoinPoint a, Object ret){
System.out.println("after"+ret);
}
public void myInterceptor(ProceedingJoinPoint p) throws Throwable{ //环绕通知
System.out.println("interceptor1");
Object ret = p.proceed(); //调用核心功能
System.out.println("interceptor2");
}
@AfterThrowing(value = "pc()",throwing = "ex")
public void myThrows(JoinPoint jp,Exception ex){ //异常通知
System.out.println("throws");
System.out.println("====="+ex.getMessage());
}
}
给AOP注解添加Spring配置才能使用
<!-- 添加如下配置,启动AOP注解-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
Spring集成JUnit
导入依赖
<!--Junit单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
编码
可以免去工厂的创建过程
可以直接将要测试的组件注入到测试类
// 测试启动 启动Spring工厂 并且当前测试类也会被工厂启动
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-context.xml")
public class TestSpringMyBatis {
//这两个注解是将属性UserService属性注入
@Autowired
@Qualifier("userService")
private UserService userService;
@Test
public void testUserService2(){
List<User> users = userService.queryUser();
for (User user : users) {
System.out.println(user);
}
}
}
测试结果 |
---|
我是雷雨,一个
普本科
的学生,主要专注于Java后端和大数据开发
如果这篇文章有帮助到你,希望你给我一个
大大的赞
如果有什么问题,希望你能留言
和我一起研究
,学习靠自觉,分享靠自愿