1. 简介
S pring 和 M ybaits 整合
2. 创建项目
负责将代理类记性扫描,扫描的是 Mapper 接口所在的包,这个是 mybatis 提供的,所以会去找 Sql S ession F actory
2 .1 mybaits 和 Spring 整合的 jar 包
mybaits 和 S pring 整合的官网: mybatis-spring –
2 .1.1 思路:
2 .1.2 M yBatis -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>
还需要 S pring 对 O RM 框架支持的 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 测试是否能连接上数据库
packagecom.hy.test;
importjava.sql.Connection;
importjava.sql.SQLException;
importjavax.sql.DataSource;
importorg.junit.Test;
importorg.junit.runner.RunWith;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.test.context.ContextConfiguration;
importorg.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner. class )
@ContextConfiguration("classpath:spring-mybatis.xml")
public class Test01 {
@Autowired
privateDataSource dataSource;
privateLogger logger = LoggerFactory. getLogger ( this .getClass());
@Test
public void testConnection() throws SQLException {
Connection connection = dataSource.getConnection();
logger.debug(connection.toString());
}
}
3. 配置 Sql S ession F actory B ean
Sql S ession F actory B ean 是由 My B atis 提供的 package org.mybatis.spring;
Sql S ession F actory B ean 实现了 F actory B ean 这个接口,
这个接口是由 Spring 提供的。
会调用 get O bject 方法得到一个 对象,这个对象是
类型的对象。
3 .1 配置方式 [ 风格 1 ]
1 )风格 1 : 保留 Mybaits 全局配置文件 ( 核心配置文件 m ybatis-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 配置 Sql S ession F actory B ean
<!-- 配置 SqlSessionFactoryBean 创建的是 sqlSessionFactory ,但是通过 sqlSessionFactory 工厂类的对象给你 S ql S ession 对象 -->
<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 扫描器 M apper ScannerConfiguration
配置 Mapper 接口类型的 bean 扫描器
<!-- 配置 Mapper 接口类型的 bean 扫描器 -->
<bean id= "mapperScannerConfigurer" class= "org.mybatis.spring.mapper.MapperScannerConfigurer" >
<property name= "basePackag e" value= "com.hy.mapper" />
</bean>
3 .1.4 创建 c om.hy.bean. E mp & Emp M apper 接口 & EmpMapper.xml映射文件
packagecom.hy.bean;
importlombok.AllArgsConstructor;
importlombok.Getter;
importlombok.NoArgsConstructor;
importlombok.Setter;
importlombok.ToString;
@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@ToString
public class Emp {
privateLong empId; // 用包装类有 null 值
privateString empName;
privateString empPwd;
privateString empGender;
privateDouble empSalary; // 用包装类有 null 值
// 构造方法 ( 去 ID 的 )
publicEmp(String empName, String empPwd, String empGender, Double empSalary) {
super();
this.empName = empName;
this.empPwd = empPwd;
this.empGender = empGender;
this.empSalary = empSalary;
}
}
packagecom.hy.mapper;
importjava.util.List;
importjava.util.Map;
importcom.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 测试
packagecom.hy.test;
importjava.sql.SQLException;
importorg.junit.Test;
importorg.junit.runner.RunWith;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.test.context.ContextConfiguration;
importorg.springframework.test.context.junit4.SpringJUnit4ClassRunner;
importcom.hy.bean.Emp;
importcom.hy.mapper.EmpMapper;
@RunWith(SpringJUnit4ClassRunner. class )
@ContextConfiguration("classpath:spring-mybatis.xml")
public class Test02 {
@Autowired
privateEmpMapper empMapper;
privateLogger 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 全局配置文件 ( 核心配置文件 m ybatis-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 自带的扫描器 Mapper S canner Configurer 扫描
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实现类
packagecom.hy.service;
importcom.hy.bean.Emp;
public interface EmpService {
abstract public Emp listById( long empId);
}
@Service
public class EmpServiceImpl implements EmpService{
@Autowired
privateEmpMapper empMapper;
publicEmp listById( long empId) {
Emp emp = empMapper.selectById(empId);
returnemp;
}
}
6.2 扫描 @ Service 组件
6.3 测试
packagecom.hy.test;
importjava.sql.SQLException;
importorg.junit.Test;
importorg.junit.runner.RunWith;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.test.context.ContextConfiguration;
importorg.springframework.test.context.junit4.SpringJUnit4ClassRunner;
importcom.hy.bean.Emp;
importcom.hy.service.EmpService;
@RunWith(SpringJUnit4ClassRunner. class )
@ContextConfiguration("classpath:spring-mybatis.xml")
public class Test03 {
@Autowired
privateEmpService empService;
privateLogger logger = LoggerFactory. getLogger ( this .getClass());
@Test
public void testEmpService() throws SQLException {
Emp emp = empService.listById(1);
logger.debug(emp.toString());
}
}
6 .4 E mp M apper 添加两个更新方法 & EmpMapper.xml
packagecom.hy.mapper;
importjava.util.List;
importjava.util.Map;
importorg.apache.ibatis.annotations.Param;
importcom.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(" e mpSalary")Double emp Salary);
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 E mp S ervice 接口 & EmpServiceImpl
packagecom.hy.service;
importcom.hy.bean.Emp;
public interface EmpService {
abstract public Emp listById( long empId);
abstract public int eidtEmp(Emp emp);
}
在 edit E mp 方法中我们会根据 emp I d ,两次修改 sys _emp表列的值
packagecom.hy.service.impl;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.stereotype.Service;
importcom.hy.bean.Emp;
importcom.hy.mapper.EmpMapper;
importcom.hy.service.EmpService;
@Service
public class EmpServiceImpl implements EmpService{
@Autowired
privateEmpMapper empMapper;
publicEmp listById( long empId) {
Emp emp = empMapper.selectById(empId);
returnemp;
}
@Override
public int eidtEmp(Emp emp) {
empMapper.updateEmpNameById(emp.getEmpId(), emp.getEmpName());
empMapper.updateEmpSalaryById(emp.getEmpId(), emp.getEmpSalary());
return 0;
}
}
6 .6 测试类 , 测试不使用事务的情况
packagecom.hy.test;
importjava.sql.SQLException;
importorg.junit.Test;
importorg.junit.runner.RunWith;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.test.context.ContextConfiguration;
importorg.springframework.test.context.junit4.SpringJUnit4ClassRunner;
importcom.hy.bean.Emp;
importcom.hy.service.EmpService;
@RunWith(SpringJUnit4ClassRunner. class )
@ContextConfiguration("classpath:spring-mybatis.xml")
public class Test03 {
@Autowired
privateEmpService empService;
privateLogger 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 基于注解的声明式事务
事务通常都是加到业务逻辑层,针对 X xxService 类使用事务
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>
S pring 持久化层支持的 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 给 Emp S er viceImpl方法上加上注解@Transactional
给 X xx S ervice I mpl 类的方法中加上 @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 )开启事务的方法: d oBegin
2 )提交事务的方法
3)回滚事务的方法
8.7 查询开启事务
8.7.1 创建一个 E mp Service & E mp S er viceImpl
packagecom.hy.service;
public interface EmpService {
abstract public Emp listById( long empId);
}
packagecom.hy.service.impl;
importorg.springframework.stereotype.Service;
importorg.springframework.transaction.annotation.Transactional;
importcom.hy.mapper.EmpMapper;
importcom.hy.service.EmpService;
@Service
public class EmpServiceImpl implements EmpService {
@Autowired
privateEmpMapper empMapper;
@Transactional
@Override
publicEmp listById( long empId) {
Emp emp = empMapper.selectById(empId);
returnemp;
}
}
获取数据库链接
切换数据库链接为手动提交
提交事务
释放链接
[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 事务属性:只读
对一个查询操作 来说,如果我们把它设置成只读,就能明确告诉数据库,这个操作不涉及写 ( 添加,删除,修改 )操作,这样数据库就能够针对查询操作来进行优化。
read O nly 并不是所有数据库都支持。
9 .1.1 设置方式
@ Transaction(readOnly = true) //readOnly = true 把当前事务属性设置为只读,默认为 false
9 .1.2 针对增删改操作设置只读属性
加了只读注解后,会有哪些影响呢?
比如做报表或者做统计:
只读事务的好处,作为 ORM 框架优化的暗号,保证读一致性,事务内不允许 DML 操作,否则报错
只读事务的场景,如统计,保证统计结果准确性。
只读事务里 , 也可以在只读事务里使用 select... for update
因为只读事务,所有查询都是在一个事务里,所以可以配合 mysql 的事务隔离级别理解一下
( 比如,你的 mysql 隔离事务是 RR 的,那么在只读事务注解里,多次查询同一个表的范围数据 , 结果是一致的,如果不是在同一个事务里,那么前后查询可能会读到的数据条数不一致,造成幻读 ) ,如果隔离级别是 RC 的,可以不用在只读事务里,因为每次查询都会读取到已提交的数据
1 0 . @ T ran sactional注解放在类上
1 0.1 生效原则
如果一个类中每一个方法上都使用了 @ Transactional 注解,那么就可以将 @ T ransactional 注解提取到类上,反过来说: @ Transactional 注解在类级别上标记,会影响到类中的每一个方法。同时,类级别标记的 @ Transactional 注解中设置的事务属性也会延续影响到方法执行时的事务属性。除非在方法上由设置了 @ Transactional注解。
对一个方法来说,离它最近的 @ Transactional注解中的事务属性设置生效。
1 0.2 用法举例
在类级别 @ Transactional 注解中设置只读,这样类中所有的查询方法都不需要设置 @ T ran sactional注解了,因为对查询操作来说,其他属性通常不需要设置,所以使用公共设置即可。
然后在这个基础上,对增删改方法设置 @ Transactional 注解 read O nly 属性为 false 。
1 1.事务的属性:超时
事务在执行过程中,有可能因为遇到某些问题,导致程序卡主,从而长时间占用数据库资源,大概的原因可能是因为程序运行出现了问题( Java 或是 My SQL)或是网络出现问题。
此时,这个很可能出问题的程序应该被执行回滚操作,撤销它已做的操作,事务回滚,把资源让出来,让其他正常程序可以执行。
总计:超时回滚,释放资源。别让一个事务占用一个资源太长的时间。
单位是秒。
1 2. 事务属性:回滚和不回顾你的异常
默认情况:只针对运行时异常进行事务回滚,编译时异常不回滚。
/ / 抛出编译时异常,测试是否回滚
n ew FileInputStream(“xxxxx”); 方法后面 throws FileNotFoundException
将回滚的异常扩大到 Exception 的范围。
1 2.1 设置回滚的异常
1 2.2 设置不回滚的异常
1 2.3 回滚和不回滚异常同时设置
1 3. 事务属性:事务的隔离级别:
事务的隔离级别和事务的传播行为,都是指事务和事务之间的关系。 之前说的事务的属性,超时,回滚,只读都是事务考虑一个事务内部之前是事情。