1. 简介
Spring 和 Mybaits整合
2. 创建项目
负责将代理类记性扫描,扫描的是Mapper接口所在的包,这个是mybatis提供的,所以会去找SqlSessionFactory
2.1 mybaits和Spring整合的jar包
mybaits和 Spring整合的官网:http://mybatis.org/spring/zh/index.html
2.1.1 思路:
2.1.2 MyBatis-Spring
这个jar包是mybaits提供的。
2.2 mybatis和spring整合所需要的jar包
要和 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>
还需要 Spring 对ORM框架支持的jar包
<!-- Spring orm Spring提供spring-orm提供orm框架相关的支持。支持Hibernate、iBatis和JPA等 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>5.2.20.RELEASE</version> </dependency> |
2.3 spring和mybaits整合,完整的pom文件
<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>com.hy</groupId> <artifactId>ssm01</artifactId> <version>0.0.1</version> <packaging>war</packaging>
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <maven.compiler.encoding>UTF-8</maven.compiler.encoding> </properties>
<dependencies> <!-- Spring core --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.20.RELEASE</version> </dependency>
<!-- Spring orm Spring提供spring-orm提供orm框架相关的支持。支持Hibernate、iBatis和JPA等 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>5.2.20.RELEASE</version> </dependency>
<!-- spring-aspects会帮我们传递过来aspectjweaver --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.20.RELEASE</version> </dependency>
<!-- mybaits相关jar包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency>
<!-- mybaits和Spring整合包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.7</version> </dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> </dependency>
<!-- 数据库连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.9</version> </dependency>
<!-- 连接数据库驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.49</version> </dependency>
<!-- Spring-Test --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.20.RELEASE</version> </dependency>
<!-- Junit测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency>
<!-- logback日志 --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency>
<!-- Mybatis EHCache整合包 --> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.2.1</version> </dependency> </dependencies>
<build> <plugins> <!-- 指定jdk,防止update project --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> <!-- 项目编码 --> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build> </project> |
2.4 创建jdbc.properties 和 logback.xml日志文件
<?xml version=\"1.0\" encoding=\"UTF-8\"?> <configuration debug=\"true\"> <!-- 指定日志输出的位置 --> <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\"> <encoder> <!-- 日志输出的格式 --> <!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 --> <pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern> </encoder> </appender>
<!-- 设置全局日志级别。日志级别按顺序分别是:TRACE > DEBUG > INFO > WARN > ERROR > FATAL --> <!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 --> <root level=\"INFO\"> <!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender --> <appender-ref ref=\"STDOUT\" /> </root>
<!-- 根据特殊需求指定局部日志级别 --> <logger name=\"com.hy.mapper\" level=\"DEBUG\" /> <logger name=\"com.hy.test\" level=\"DEBUG\" /> </configuration> |
2.5 首先建立spring和mybaits的配置的文件 spring-mybatis.xml
2.5.1 加载外部属性文件
2.5.2 配置数据源
<?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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd\">
<!-- 加载外部属性文件 --> <context:property-placeholder location=\"classpath:jdbc.properties\"/>
<!-- 配置数据源 --> <bean id=\"druidDataSource\" class=\"com.alibaba.druid.pool.DruidDataSource\"> <property name=\"driverClassName\" value=\"${jdbc.dev.driver}\"/> <property name=\"url\" value=\"${jdbc.dev.url}\"/> <property name=\"username\" value=\"${jdbc.dev.username}\"/> <property name=\"password\" value=\"${jdbc.dev.password}\"/> </bean> </beans> |
2.5.3 测试是否能连接上数据库
package com.hy.test;
import java.sql.Connection; import java.sql.SQLException;
import javax.sql.DataSource;
import org.junit.Test; import org.junit.runner.RunWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(\"classpath:spring-mybatis.xml\") public class Test01 { @Autowired private DataSource dataSource;
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Test public void testConnection() throws SQLException { Connection connection = dataSource.getConnection();
logger.debug(connection.toString()); } }
|
3. 配置SqlSessionFactoryBean
SqlSessionFactoryBean 是由MyBatis提供的package org.mybatis.spring;
SqlSessionFactoryBean 实现了 FactoryBean这个接口,
这个接口是由Spring提供的。
会调用 getObject方法得到一个 对象,这个对象是
类型的对象。
3.1 配置方式[风格1]
1)风格1:保留Mybaits全局配置文件(核心配置文件mybatis-config.xml)
3.1.1 创建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> <settings> <!-- 数据库中的字段的_规则,转类中属性的驼峰标示写法 --> <setting name=\"mapUnderscoreToCamelCase\" value=\"true\"/> </settings> </configuration> |
3.1.2 配置SqlSessionFactoryBean
<!-- 配置SqlSessionFactoryBean 创建的是 sqlSessionFactory,但是通过sqlSessionFactory 工厂类的对象给你 SqlSession对象--> <bean id=\"sqlSessionFactoryBean\" class=\"org.mybatis.spring.SqlSessionFactoryBean\"> <!-- 装配数据源 --> <property name=\"dataSource\" ref=\"druidDataSource\"/>
<!-- 指定Mapper 映射文件的位置 --> <property name=\"mapperLocations\" value=\"classpath:mappers/*Mapper.xml\"/>
<!-- 指定 MyBatis 全局配置文件位置 --> <property name=\"configLocation\" value=\"classpath:mybatis-config.xml\"/> </bean> |
3.1.3 配置Mapper接口类型的bean扫描器MapperScannerConfiguration
配置 Mapper接口类型的bean扫描器
<!-- 配置 Mapper接口类型的bean扫描器 --> <bean id=\"mapperScannerConfigurer\" class=\"org.mybatis.spring.mapper.MapperScannerConfigurer\"> <property name=\"basePackage\" value=\"com.hy.mapper\"/> </bean> |
3.1.4 创建com.hy.bean.Emp & EmpMapper接口 & EmpMapper.xml映射文件
package com.hy.bean;
import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString;
@NoArgsConstructor @AllArgsConstructor @Setter @Getter @ToString public class Emp { private Long empId; //用包装类有null值 private String empName; private String empPwd; private String empGender; private Double empSalary; //用包装类有null值
//构造方法(去ID的) public Emp(String empName, String empPwd, String empGender, Double empSalary) { super(); this.empName = empName; this.empPwd = empPwd; this.empGender = empGender; this.empSalary = empSalary; } } |
package com.hy.mapper;
import java.util.List; import java.util.Map;
import com.hy.bean.Emp;
public interface EmpMapper { abstract public Emp selectById(long empId);
abstract public int insert(Emp emp);
abstract public int deleteById(long empId);
abstract public int update(Emp emp);
abstract public int updateByMap(Map<String, Object> paramMap);
abstract public Integer selectCount();
abstract public Map<String, Object> selectForMap(int empId);
abstract public List<Emp> selectAll();
abstract public int insertWithKey(Emp emp); } |
<?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=\"com.hy.mapper.EmpMapper\"> <select id=\"selectById\" resultType=\"com.hy.bean.Emp\"> select emp_id empId,emp_name empName,emp_pwd empPwd,emp_gender empGender , emp_salary empSalary from sys_emp where emp_id = #{empId} </select> </mapper> |
3.1.5 测试
package com.hy.test;
import java.sql.SQLException;
import org.junit.Test; import org.junit.runner.RunWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.hy.bean.Emp; import com.hy.mapper.EmpMapper;
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(\"classpath:spring-mybatis.xml\") public class Test02 { @Autowired private EmpMapper empMapper;
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Test public void testEmpMapper () throws SQLException { Emp emp = empMapper.selectById(1);
logger.debug(emp.toString()); } } |
3.1 配置方式[风格2]
2)风格2:彻底舍弃Mybaits全局配置文件(核心配置文件mybatis-config.xml),所有的一切在spring的配置文件中配。
<!-- 配置SqlSessionFactoryBean --> <bean id=\"sqlSessionFactoryBean\" class=\"org.mybatis.spring.SqlSessionFactoryBean\"> <!-- 指定 MyBatis 全局配置文件位置 --> <!-- <property name=\"configLocation\" value=\"classpath:mybatis-config.xml\"/> -->
<!-- 舍弃mybatis-config全局配置文件,使用configuration属性 --> <property name=\"configuration\"> <bean class=\"org.apache.ibatis.session.Configuration\"> <property name=\"mapUnderscoreToCamelCase\" value=\"true\"/> </bean> </property>
<!-- 舍弃mybatis-config全局配置文件,使用typeAliasesPackage属性,配置实体bean的别名 --> <property name=\"typeAliasesPackage\" value=\"com.hy.bean\"/>
<!-- 指定Mapper 映射文件的位置 --> <property name=\"mapperLocations\" value=\"classpath:mappers/*Mapper.xml\"/>
<!-- 装配数据源 --> <property name=\"dataSource\" ref=\"druidDataSource\"/> </bean> |
3.2 注意,扫描包的时候分开扫描
spring-mybaits.xml只扫描Service,
而Mapper是用mybatis自带的扫描器MapperScannerConfigurer扫描
3.3 spring-mybatis.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:context=\"http://www.springframework.org/schema/context\" xmlns:tx=\"http://www.springframework.org/schema/tx\" xsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd\">
<!-- 在spring-mybatis中 扫描@Servie注解标识的组件 --> <context:component-scan base-package=\"com.hy.service\"/>
<!-- 加载外部属性文件 --> <context:property-placeholder location=\"classpath:jdbc.properties\"/>
<!-- 配置数据源 --> <bean id=\"druidDataSource\" class=\"com.alibaba.druid.pool.DruidDataSource\"> <property name=\"driverClassName\" value=\"${jdbc.dev.driver}\"/> <property name=\"url\" value=\"${jdbc.dev.url}\"/> <property name=\"username\" value=\"${jdbc.dev.username}\"/> <property name=\"password\" value=\"${jdbc.dev.password}\"/> </bean>
<!-- 配置SqlSessionFactoryBean --> <bean id=\"sqlSessionFactoryBean\" class=\"org.mybatis.spring.SqlSessionFactoryBean\"> <!-- 指定 MyBatis 全局配置文件位置 --> <property name=\"configLocation\" value=\"classpath:mybatis-config.xml\"/>
<!-- 舍弃mybatis-config全局配置文件,使用configuration属性 <property name=\"configuration\"> <bean class=\"org.apache.ibatis.session.Configuration\"> <property name=\"mapUnderscoreToCamelCase\" value=\"true\"/> </bean> </property> --> <!-- 舍弃mybatis-config全局配置文件,使用typeAliasesPackage属性,配置实体bean的别名 <property name=\"typeAliasesPackage\" value=\"com.hy.bean\"/> -->
<!-- 指定Mapper 映射文件的位置 --> <property name=\"mapperLocations\" value=\"classpath:mappers/*Mapper.xml\"/>
<!-- 装配数据源 --> <property name=\"dataSource\" ref=\"druidDataSource\"/> </bean>
<!-- 配置 Mapper接口类型的bean扫描器 --> <bean id=\"mapperScannerConfigurer\" class=\"org.mybatis.spring.mapper.MapperScannerConfigurer\"> <property name=\"basePackage\" value=\"com.hy.mapper\"/> </bean> </beans> |
3.4 mybatis-config.xml
<?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> <settings> <!-- 数据库中的字段的_规则,转类中属性的驼峰标示写法 --> <setting name=\"mapUnderscoreToCamelCase\" value=\"true\"/> </settings> </configuration> |
4. 编程式事务
事务功能的相关操作全部通过自己编写代码来实现:
Connection conn = ...;
try { // 开启事务:关闭事务的自动提交 conn.setAutoCommit(false);
// 核心操作
// 提交事务 conn.commit(); }catch(Exception e){ // 回滚事务 conn.rollBack(); }finally{ // 释放数据库连接 conn.close(); } |
编程式的实现方式存在缺陷:
1)具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐。
2)代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复用。
5. 声明式事务
既然事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出来,进行相关的封装。
封装起来后,我们只需要在配置文件中进行简单的配置即可完成操作。
好处1:提高开发效率
好处2:消除了冗余的代码
好处3:框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题,进行了健壮性、性能等各个方面的优化
编程式事务:自己写代码实现功能
声明式事务:通过配置让框架实现功能
5.1事务管理器
5.1.1 Spring 5.2以前
5.1.2 从 Spring 5.2开始
PlatformTransactionManager 接口本身没有变化,它继承了 TransactionManager。TransactionManager接口中什么都没有,它的存在的意义是定义一个技术体系。
我们现在要使用的事务管理器是org.springframework.jdbc.datasource.DataSourceTransactionManager,将来整合 Mybatis 用的也是这个类。
5.2 DataSourceTransactionManager类中的主要方法:
doBegin():开启事务
doCommit():提交事务
doRollback():回滚事务
doSuspend():挂起事务
doResume():恢复挂起的事务
6. 添加Service层
6.1 EmpService接口 & EmpServiceImpl实现类
package com.hy.service;
import com.hy.bean.Emp; public interface EmpService { abstract public Emp listById(long empId); } |
@Service public class EmpServiceImpl implements EmpService{ @Autowired private EmpMapper empMapper;
public Emp listById(long empId) { Emp emp = empMapper.selectById(empId); return emp; } } |
6.2 扫描@Service 组件
6.3 测试
package com.hy.test;
import java.sql.SQLException;
import org.junit.Test; import org.junit.runner.RunWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.hy.bean.Emp; import com.hy.service.EmpService;
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(\"classpath:spring-mybatis.xml\") public class Test03 { @Autowired private EmpService empService; private Logger logger = LoggerFactory.getLogger(this.getClass());
@Test public void testEmpService() throws SQLException { Emp emp = empService.listById(1);
logger.debug(emp.toString()); } } |
6.4 EmpMapper添加两个更新方法 & EmpMapper.xml
package com.hy.mapper;
import java.util.List; import java.util.Map;
import org.apache.ibatis.annotations.Param;
import com.hy.bean.Emp;
public interface EmpMapper { abstract public void updateEmpNameById(@Param(\"empId\") long empId, @Param(\"empName\")String empName);
abstract public void updateEmpSalaryById(@Param(\"empId\") long empId, @Param(\"empSalary\")Double empSalary);
abstract public Emp selectById(long empId); } |
<?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=\"com.hy.mapper.EmpMapper\"> <select id=\"selectById\" resultType=\"com.hy.bean.Emp\"> select emp_id empId,emp_name empName,emp_pwd empPwd,emp_gender empGender , emp_salary empSalary from sys_emp where emp_id = #{empId} </select>
<!-- abstract public void updateEmpNameById(@Param(\"empId\")long empId, @Param(\"empName\")String empName); abstract public void updateEmpSalaryById(@Param(\"empId\")long empId, @Param(\"empSalary\")Double empsalary); --> <update id=\"updateEmpNameById\"> update sys_emp set emp_name = #{empName} where emp_id = #{empId} </update>
<update id=\"updateEmpSalaryById\"> update sys_emp set emp_salary = #{empSalary} where emp_id = #{empId} </update> </mapper>
|
6.5 EmpService接口 & EmpServiceImpl
package com.hy.service; import com.hy.bean.Emp; public interface EmpService { abstract public Emp listById(long empId);
abstract public int eidtEmp(Emp emp); } |
在editEmp方法中我们会根据empId,两次修改sys_emp表列的值
package com.hy.service.impl;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;
import com.hy.bean.Emp; import com.hy.mapper.EmpMapper; import com.hy.service.EmpService;
@Service public class EmpServiceImpl implements EmpService{ @Autowired private EmpMapper empMapper;
public Emp listById(long empId) { Emp emp = empMapper.selectById(empId); return emp; }
@Override public int eidtEmp(Emp emp) { empMapper.updateEmpNameById(emp.getEmpId(), emp.getEmpName()); empMapper.updateEmpSalaryById(emp.getEmpId(), emp.getEmpSalary()); return 0; } }
|
6.6 测试类,测试不使用事务的情况
package com.hy.test;
import java.sql.SQLException;
import org.junit.Test; import org.junit.runner.RunWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.hy.bean.Emp; import com.hy.service.EmpService;
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(\"classpath:spring-mybatis.xml\") public class Test03 { @Autowired private EmpService empService;
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Test public void testEmpService() throws SQLException { Emp emp = empService.listById(1);
logger.debug(emp.toString()); }
@Test public void testEmpService2() throws SQLException { Emp emp = new Emp(1L, \"范冰冰plus3\", \"fbbplus\", \"f\", 1315d);
empService.eidtEmp(emp); } } |
出现异常的情况
7. 事务
7.1 编程式事务
事务功能的相关操作全部通过自己编写代码来实现:
Connection conn = ...;
try { // 开启事务:关闭事务的自动提交 conn.setAutoCommit(false);
// 核心操作
// 提交事务 conn.commit(); }catch(Exception e){ // 回滚事务 conn.rollBack(); }finally{ // 释放数据库连接 conn.close(); } |
编程式的实现方式存在缺陷:
1)具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐。
2) 代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复用。
7.2 声明式事务
既然事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出来,进行相关的封装。
封装起来后,我们只需要在配置文件中进行简单的配置即可完成操作。
好处1:提高开发效率
好处2:消除了冗余的代码
好处3:框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题,进行了健壮性、性能等各个方面的优化
编程式事务:自己写代码实现功能
声明式事务:通过配置让框架实现功能
7.3 事务管理器
7.3.1 Spring 5.2以前
7.3.2 从 Spring 5.2开始
PlatformTransactionManager 接口本身没有变化,它继承了 TransactionManager。TransactionManager接口中什么都没有,它的存在的意义是定义一个技术体系。
我们现在要使用的事务管理器是org.springframework.jdbc.datasource.DataSourceTransactionManager,将来整合 Mybatis 用的也是这个类。
7.4 DataSourceTransactionManager类中的主要方法:
doBegin():开启事务
doCommit():提交事务
doRollback():回滚事务
doSuspend():挂起事务
doResume():恢复挂起的事务
事务的挂起和恢复,主要是事务传播行为所体现的。
8 基于注解的声明式事务
事务通常都是加到业务逻辑层,针对XxxService类使用事务
8.1 配置声明式事务,需要添加新的依赖
<!-- Spring orm Spring提供spring-orm提供orm框架相关的支持。支持Hibernate、iBatis和JPA等 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>5.2.20.RELEASE</version> </dependency>
|
Spring 持久化层支持的jar包,Spring在执行持久化操作与持久化技术进行整合过程中,需要使用orm,tx,jdbc三个jar包
导入orm包就可以通过maven的依赖传递把其他两个也导入进来。
8.2 给事务管理器装配一下数据源&。
<!-- 配置事务管理器 --> <bean id=\"transactionManager\" class=\"org.springframework.jdbc.datasource.DataSourceTransactionManager\"> <!-- 给事务管理器bean装配数据源,其他属性保持默认即可 --> <property name=\"dataSource\" ref=\"druidDataSource\"/> </bean> |
8.3 开启基于注解的声明式事务
<!-- 开启基于注解的声明式事务 --> <tx:annotation-driven transaction-manager=\"transactionManager\"/> |
8.4 给EmpServiceImpl方法上加上注解@Transactional
给XxxServiceImpl类的方法中加上 @Transactional 注解,Spring会自动的给这个方法加上事务。
8.6 完整的配置spring-mybaits.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:context=\"http://www.springframework.org/schema/context\" xmlns:tx=\"http://www.springframework.org/schema/tx\" xsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd\">
<!-- 在spring-mybatis中 扫描@Servie注解标识的组件 --> <context:component-scan base-package=\"com.hy.service\"/>
<!-- 加载外部属性文件 --> <context:property-placeholder location=\"classpath:jdbc.properties\"/>
<!-- 配置数据源 --> <bean id=\"druidDataSource\" class=\"com.alibaba.druid.pool.DruidDataSource\"> <property name=\"driverClassName\" value=\"${jdbc.dev.driver}\"/> <property name=\"url\" value=\"${jdbc.dev.url}\"/> <property name=\"username\" value=\"${jdbc.dev.username}\"/> <property name=\"password\" value=\"${jdbc.dev.password}\"/> </bean>
<!-- 配置SqlSessionFactoryBean --> <bean id=\"sqlSessionFactoryBean\" class=\"org.mybatis.spring.SqlSessionFactoryBean\"> <!-- 指定 MyBatis 全局配置文件位置 --> <property name=\"configLocation\" value=\"classpath:mybatis-config.xml\"/>
<!-- 舍弃mybatis-config全局配置文件,使用configuration属性 <property name=\"configuration\"> <bean class=\"org.apache.ibatis.session.Configuration\"> <property name=\"mapUnderscoreToCamelCase\" value=\"true\"/> </bean> </property> --> <!-- 舍弃mybatis-config全局配置文件,使用typeAliasesPackage属性,配置实体bean的别名 <property name=\"typeAliasesPackage\" value=\"com.hy.bean\"/> -->
<!-- 指定Mapper 映射文件的位置 --> <property name=\"mapperLocations\" value=\"classpath:mappers/*Mapper.xml\"/>
<!-- 装配数据源 --> <property name=\"dataSource\" ref=\"druidDataSource\"/> </bean>
<!-- 配置 Mapper接口类型的bean扫描器 --> <bean id=\"mapperScannerConfigurer\" class=\"org.mybatis.spring.mapper.MapperScannerConfigurer\"> <property name=\"basePackage\" value=\"com.hy.mapper\"/> </bean>
<!-- 配置事务管理器 --> <bean id=\"transactionManager\" class=\"org.springframework.jdbc.datasource.DataSourceTransactionManager\"> <!-- 装配数据源 --> <property name=\"dataSource\" ref=\"druidDataSource\"/> </bean>
<!-- 开启基于注解的声明式事务 --> <tx:annotation-driven transaction-manager=\"transactionManager\"/> </beans> |
8.6 测试
8.6.1 配置日志观察效果
<!-- 根据特殊需求指定局部日志级别 --> <logger name=\"com.hy.mapper\" level=\"DEBUG\" /> <logger name=\"com.hy.test\" level=\"DEBUG\" /> <logger name=\"org.springframework.jdbc.datasource.DataSourceTransactionManager\" level=\"DEBUG\" /> |
8.6.2 通过日志观察事务
8.6.3 通过debug源码的模式观察事务
1)开启事务的方法:doBegin
2)提交事务的方法
3)回滚事务的方法
8.7 查询开启事务
8.7.1 创建一个EmpService & EmpServiceImpl
package com.hy.service;
public interface EmpService { abstract public Emp listById(long empId); } |
package com.hy.service.impl;
import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;
import com.hy.mapper.EmpMapper; import com.hy.service.EmpService;
@Service public class EmpServiceImpl implements EmpService { @Autowired private EmpMapper empMapper;
@Transactional @Override public Emp listById(long empId) { Emp emp = empMapper.selectById(empId); return emp; } } |
获取数据库链接
切换数据库链接为手动提交
提交事务
释放链接
[org.springframework.jdbc.datasource.DataSourceTransactionManager] [Creating new transaction with name [com.hy.service.impl.EmpServiceImpl.listById]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly]
这里为了现实效果,所以为查询配置了事务。
9 事务的属性,这些属性可以设置但不是必须设置
1) 事务的只读属性
2) 事务的超时属性
3) 回滚和不回滚的异常
4) 事务的隔离级别
5) 事务传播行为
9.1 事务属性:只读
对一个查询操作来说,如果我们把它设置成只读,就能明确告诉数据库,这个操作不涉及写(添加,删除,修改)操作,这样数据库就能够针对查询操作来进行优化。
readOnly并不是所有数据库都支持。
9.1.1 设置方式
@Transaction(readOnly = true) //readOnly = true把当前事务属性设置为只读,默认为false
9.1.2 针对增删改操作设置只读属性
加了只读注解后,会有哪些影响呢?
比如做报表或者做统计:
只读事务的好处,作为ORM框架优化的暗号,保证读一致性,事务内不允许DML操作,否则报错 只读事务的场景,如统计,保证统计结果准确性。 只读事务里,也可以在只读事务里使用 select... for update 因为只读事务,所有查询都是在一个事务里,所以可以配合mysql的事务隔离级别理解一下 (比如,你的mysql隔离事务是RR的,那么在只读事务注解里,多次查询同一个表的范围数据, 结果是一致的,如果不是在同一个事务里,那么前后查询可能会读到的数据条数不一致,造成幻读),如果隔离级别是RC的,可以不用在只读事务里,因为每次查询都会读取到已提交的数据 |
10.@Transactional注解放在类上
10.1 生效原则
如果一个类中每一个方法上都使用了@Transactional注解,那么就可以将@Transactional注解提取到类上,反过来说:@Transactional注解在类级别上标记,会影响到类中的每一个方法。同时,类级别标记的@Transactional注解中设置的事务属性也会延续影响到方法执行时的事务属性。除非在方法上由设置了@Transactional注解。
对一个方法来说,离它最近的@Transactional注解中的事务属性设置生效。
10.2 用法举例
在类级别@Transactional注解中设置只读,这样类中所有的查询方法都不需要设置@Transactional注解了,因为对查询操作来说,其他属性通常不需要设置,所以使用公共设置即可。
然后在这个基础上,对增删改方法设置@Transactional注解readOnly属性为false。
11.事务的属性:超时
事务在执行过程中,有可能因为遇到某些问题,导致程序卡主,从而长时间占用数据库资源,大概的原因可能是因为程序运行出现了问题(Java或是MySQL)或是网络出现问题。
此时,这个很可能出问题的程序应该被执行回滚操作,撤销它已做的操作,事务回滚,把资源让出来,让其他正常程序可以执行。
总计:超时回滚,释放资源。别让一个事务占用一个资源太长的时间。
单位是秒。
12. 事务属性:回滚和不回顾你的异常
默认情况:只针对运行时异常进行事务回滚,编译时异常不回滚。
//抛出编译时异常,测试是否回滚
new FileInputStream(“xxxxx”); 方法后面throws FileNotFoundException
将回滚的异常扩大到Exception的范围。
12.1 设置回滚的异常
12.2 设置不回滚的异常
12.3 回滚和不回滚异常同时设置
13. 事务属性:事务的隔离级别:
事务的隔离级别和事务的传播行为,都是指事务和事务之间的关系。 之前说的事务的属性,超时,回滚,只读都是事务考虑一个事务内部之前是事情。
来源:https://www.cnblogs.com/lijili/p/16693077.html
本站部分图文来源于网络,如有侵权请联系删除。