【Spring 持久层整合】整合MyBatis、事务操作

参考自B站视频《孙哥说Spring5》

持久层整合总述

1、Spring 框架为什么要与持久层技术进行整合?

  • JavaEE开发需要持久层进行数据库的访问操作
  • JDBCHibernateMyBatis 进行持久开发过程存在大量的代码冗余
  • Spring 基于模板设计模式对于上述的持久层技术进行了封装

2、Spring 可以与哪些持久层技术进行整合?

  • JDBC —— JDBCTemplate
  • Hibernate(JPA)—— HibernateTemplate
  • MyBatis —— SqlSessionFactoryBeanMapperScannerConfigure

MyBatis 开发步骤

  1. 实体类
public class User implements Serializable {
    private Integer id;
    private String name;
    private String password;

    public User() {
    }

    public User(Integer id, String name, String password) {
        this.id = id;
        this.name = name;
        this.password = password;
    }

    // get set 方法 ...
}
  1. 配置文件 mybatis-config.xml 配置实体别名和注册mapper
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Confi 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<!-- 别名 -->
    <typeAliases>
        <typeAlias alias="user" type="com.yusael.mybatis.User"/>
    </typeAliases>
    <!-- JDBC连接环境 -->
    <environments default="mysql">
        <environment id="mysql">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/yus?useSSL=false"/>
                <property name="username" value="root"/>
                <property name="password" value="1234"/>
            </dataSource>
        </environment>
    </environments>
</configuration>
  1. 数据库表
create table t_users values (
	id int(11) primary key auto_increment,
	name varchar(12),
	password varchar(12)
);
  1. 创建DAO接口
public interface UserDAO {
    public void save(User user);
}
  1. 实现Mapper文件 UserDAOMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.yusael.mybatis.UserDAO">
    <insert id="save" parameterType="user">
        insert into t_users(name, password) values (#{name}, #{password})
    </insert>
</mapper>
  1. 注册Mapper文件 mybatis-config.xml
<mappers>
	<mapper resource="UserDAOMapper.xml"/>
</mappers>
  1. MyBatis API调用
public class TestMybatis {
    public static void main(String[] args) throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        // 会话工厂
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 打开会话
        SqlSession session = sqlSessionFactory.openSession();
        // 获得 DAO 层
        UserDAO userDAO = session.getMapper(UserDAO.class);
        // 创建实体类
        User user = new User();
        user.setName("yusael");
        user.setPassword("123456");
        // mybatis 保存操作
        userDAO.save(user);
        // 提交事务
        session.commit();
    }
}

以上开发步骤中存在的问题

配置繁琐、代码冗余

1. 实体
2. 实体别名					配置繁琐
3. 表
4. 创建 DAO 接口
5. 实现 Mapper 文件
6. 注册 Mapper 文件			配置繁琐
7. Mybatis API 调用			代码冗余

Spring 整合 MyBatis

思路

Spring 主要就是在Spring配置文件中简化 MyBatis 的步骤和代码

在这里插入图片描述

在这里插入图片描述

1. 实体
2. 实体别名				 不需要
3. 表
4. 创建 DAO 接口
5. 实现 Mapper 文件
6. 注册 Mapper 文件			不需要
7. Mybatis API 调用			直接调用DAO接口即可

开发步骤

导入依赖 pom.xml

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-jdbc</artifactId>
  <version>5.2.6.RELEASE</version>
</dependency>

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>2.0.4</version>
</dependency>

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
  <version>1.1.12</version>
</dependency>

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.43</version>
</dependency>

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>3.5.4</version>
</dependency>

Spring配置文件

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd">
        <!--连接池-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/yus?useSSL=false"/>
            <property name="username" value="root"/>
            <property name="password" value="1234"/>
        </bean>

        <!--创建SqlSessionFactory SqlSessionFactoryBean-->
        <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="dataSource"/>
            <!-- 指定实体类所在的包 -->
            <property name="typeAliasesPackage" value="com.yusael.entity"/>
            <!--指定配置文件(映射文件)的路径,还有通用配置-->
            <property name="mapperLocations">
                <list>
                	<!-- 通配符匹配 -->
                    <value>classpath:com.yusael.dao/*Mapper.xml</value>
                </list>
            </property>
        </bean>

        <!--创建DAO对象 MapperScannerConfigure-->
        <bean id="scanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean"/>
            <!--指定DAO接口放置的包-->
            <property name="basePackage" value="com.yusael.dao"/>
        </bean>

</beans>

Java类编码

实体类
public class User implements Serializable {
    private Integer id;
    private String name;
    private String password;
	
	// get set ...
}

DAO接口
public interface UserDAO {
    public void save(User user);
}

数据库表

create table t_users values (
	id int(11) primary key auto_increment,
	name varchar(12),
	password varchar(12)
);

Mapper 映射文件

<mapper namespace="com.baizhiedu.dao.UserDAO">
	<insert id="save" parameterType="User">
		insert into t_users (name, password) values (#{name}, #{password})
	</insert>
</mapper>

测试类

/**
 * 用于测试: Spring 与 Mybatis 的整合
 */
@Test
public void test() {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
    UserDAO userDAO = (UserDAO) ctx.getBean("userDAO");

    User user = new User();
    user.setName("xiaojr");
    user.setPassword("999999");

    userDAO.save(user);
}

Spring 整合 MyBatis 细节

Spring 与 MyBatis 整合之后,为什么DAO不提交事务,但是数据能够插入数据库中

  • 我们可以知道,事务是由连接控制的

  • 同时我们查看Spring的日志输出可以知道Spring并没有管理连接
    在这里插入图片描述

  • 其实是 MyBatis 提供的连接池管理的连接对象

  • MyBatis 在创建连接对象的时候会执行 Connection.setAutoCommit(false) 手动控制事务:操作完成后,需要手工提交。

  • Spring整合MyBatis后是由 Druid(C3P0、DBCP) 作为连接池,创建连接对象

  • 而 Druid 默认是设为 true 的,保持自动控制事务,完成一条 sql 操作后就会自动提交

答:因为 Spring 与 Mybatis 整合时,引入了外部连接池对象,保持自动的事务提交这个机制Connection.setAutoCommit(true),不需要手工进行事务的操作,也能进行事务的提交。

注意:实战中,还是会手工控制事务(可能需要多条SQL一起成功,一起失败),后续 Spring 通过 事务控制 解决这个问题。

事务概念

什么是事务?

  • 事务是保证业务操作完整性的一种数据库机制

事务的四大特点:ACID(原子性、一致性、隔离性、持久性)

如何控制事务?

  • JDBC
Connection.setAutoCommit(false); // 开启事务
Connection.commit(); // 提交
Connection.rollback(); // 回滚
  • MyBatis
Mybatis 自动开启事务 --> 手动提交
sqlSession.commit();  底层还是调用的 Connection
sqlSession.rollback();  底层还是调用的 Connection
  • 控制事务的底层,都是通过 Connection 对象完成的

Spring 事务开发

Spring 是通过AOP的方式进行事务的开发

对应AOP开发步骤:原始对象(DAO)、额外功能(AOP方式开启、提交、回滚)、切入点组装切面

如果采用传统AOP的开发方式,需要实现 MethodInterceptor 接口,通过注入连接池获取连接对象

然后在invoke方法中前后加上事务的操作,这样就完成了额外功能这一步骤

但是事务的操作都是基本一致的,因此Spring为我们封装好了传统AOP的方式以完成事务功能,我们可以直接使用Spring提供的注解@Transactional,简化开发

开发步骤

导入依赖环境

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-tx</artifactId>
	<version>5.2.6.RELEASE</version>
</dependency>

Service 切入点

// 标记切入点 类上就会给所有方法都加入事务 方法上就会给该方法加入事务
@Transactional
public class UserServiceImpl implements UserService {
    private UserDAO userDAO;

	@Override
	public void register(User user){
		userDAO.save(user);
	}
}

配置类中配置事务

<!-- 原始对象 -->
<bean id="userService" class="com.yusael.service.UserServiceImpl">
	<!-- userDAO 注入就是通过前面的 MapperScannerConfigure 扫描的 mapper 包中获得 id为类名首字母小写 -->
	<property name="userDAO" ref="userDAO"/>
</bean>

<!--DataSourceTransactionManager 额外功能 注入连接池以获得连接对象-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 打开注解开发 transaction-manager获取额外功能  将额外功能与注解标记的切入点组合 -->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>

因为是通过AOP实现的,所以可以切换动态代理层实现的两个方式(JDK、CGlib)

<tx:annotation-driven transaction-manager="dataSourceTransactionManager" proxy-target-class="true"/>

Spring中的事务属性(Transaction Attribute)

什么是事务属性?

  • 属性:描述物体特征的一系列值(性别、身高、体重)
  • 事务属性:描述事务特征的一系列值
  1. 隔离属性
  2. 传播属性
  3. 只读属性
  4. 超时属性
  5. 异常属性

如何添加事务属性?

@Transactional(isolation=, propagation=, readOnly=,timeout=,rollbackFor,noRollbackFor=,)

隔离属性

概念:描述了事务解决并发问题的特征

什么是并发?

  • 多个事务(用户)在同一时间,访问操作了相同的数据
    同一时间:0.000几秒左右 微乎其微

并发会产生那些问题?

  • 脏读
  • 不可重复读
  • 幻影读

并发问题如何解决?

  • 通过隔离属性解决,隔离属性中设置不同的值,解决并发处理的过程中的问题。

事务并发产生的问题:

  • 脏读
    一个事务,读取了另一个事务中没有提交的数据,会在本事务中产生数据不一样的现象
    解决方案:@Transaction(isolation=Isolation.READ_COMMITTED)
    • 要求只能读取提交的数据
  • 不可重复读
    一个事务中,多次读取相同的数据,但是读取结果不一样,会在本事务中产生数据不一样的现象
    注意:1.不是脏读 2.在一个事务中
    解决方案:@Transaction(isolation=Isolation.REPEATABLE_READ)
    • 就是加上一把行锁(对数据库表的某一行加锁)
  • 幻影读
    一个事务中,多次整表进行查询统计,但是结果不一样,会在本事务中产生数据不一致的问题
    解决方案:@Transaction(isolation=Isolation.SERIALIZABLE)
    • 就是一把表锁(对数据库某个表加锁)

安全与效率对比:

  • 并发安全:SERIALIZABLE > READ_ONLY > READ_COMMITTED
  • 运行效率:READ_COMMITTED > READ_ONLY > SERIALIZABLE

数据库对隔离属性的支持:

隔离属性的值MySQLOracle
ISOLATION_READ_COMMITTED支持支持
ISOLATION_REPEATABLE_READ支持不支持
ISOLATION_SERIALIZABLE支持支持

Oracle 不支持 REPEATABLE_READ,那该如何解决不可重复读?

  • 采用 多版本比对 的方式解决不可重复读问题。

默认的隔离属性

Spring 会指定为 ISOLATION_DEFAULT,调用不同数据库所设置的默认隔离属性

  • MySQL:REPEATABLE_READ
  • Oracle:READ_COMMITTED

查看数据库的默认隔离属性:

  • MySQL:SELECT @@tx_isolation;
  • Oracle
select s.sid, s.serial#,
	case bitand(t.flag, power(2, 28))
		when 0 then 'READ COMMITED'
		else 'SERIALIZABLE'
	end as isolation_level
from v$transaction t
join v$session s on t.addr = s.taddr
and s.sid = sys_context('USERENV', 'SID');

隔离属性在实战中的建议

  • 推荐使用 Spring 默认指定的 ISOLATION_DEFAULT
  • 未来的实战中,遇到并发访问的情况,很少见
  • 如果真的遇到并发问题,解决方案:乐观锁
    Hibernate(JPA):Version
    MyBatis:通过拦截器自定义开发

传播属性(PROPAGATION)

概念:描述了事务解决 嵌套 问题 的特征。

事务的嵌套:指的是一个大的事务中,包含了若干个小的事务。

事务嵌套产生的问题: 大事务中融入了很多小的事务,他们彼此影响,最终就导致外部大的事务丧失了事务的原子性。

传播属性的值及其用法

传播属性的值外部不存在事务外部存在事务用法适用于
REQUIRED开启新的事务融合到外部事务中@Transactional(propagation = Propagation.REQUIRED)增、删、改方法
SUPPORTS不开启事务融合到外部事务中@Transactional(propagation = Propagation.SUPPORTS)查询方法
REQUIRES_NEW开启新的事务挂起外部事务,创建新的事务@Transactional(propagation = Propagation.REQUIRES_NEW)日志记录方法中
NOT_SUPPORTED不开启事务挂起外部事务@Transactional(propagation = Propagation.NOT_SUPPORTED)极其不常用
NEVER不开启事务抛出异常@Transactional(propagation = Propagation.NEVER)极其不常用
MANDATORY抛出异常融合到外部事物中@Transactional(propagation = Propagation.MANDATORY)极其不常用
  • 融合到外部事务即自己的事务删除,将步骤放到外部事务中,一起成功或者一起失败
  • 挂起外部事务就是暂停外部的事务

Spring 中传播属性的默认值是:REQUIRED

推荐传播属性的使用方式:

  • 增删改 方法:使用默认值 REQUIRED
  • 查询 方法:显式指定传播属性的值为 SUPPORTS

只读属性(readOnly)

针对于 只进行查询操作的业务方法,可以加入只读属性,提高运行效率。

  • 默认值:false
@Transactional(readOnly = true)

超时属性(timeout)

指定了事务等待的最长时间。

为什么事务会进行等待?

  • 当前事务访问数据时,有可能访问的数据被别的事务进行加锁的处理,那么此时本事务就必须进行等待。

等待时间,单位是

如何使用:@Transactional(timeout = 2)

超时属性的默认值:-1

  • 表示超时属性由对应的数据库来指定(一般不会主动指定,-1 即可)

异常属性

Spring 事务处理过程中:

  • 默认对于 RuntimeException 及其子类,采用 回滚 的策略。
  • 默认对于 Exception 及其子类,采用 提交 的策略。

使用方法:

// 当然也可以自己指定一些异常
// 采用回滚策略
@Transactional(rollbackFor = {java.lang.Exception.class, xxx, xxx})
// 不回滚即采用提交的策略
@Transactional(noRollbackFor = {java.lang.RuntimeException, xxx, xxx})

建议:实战中使用 RuntimeException 及其子类,使用事务异常属性的默认值。

事务属性常见配置总结

  1. 隔离属性 默认值
  2. 传播属性 Required(默认值)增删改、Supports 查询操作
  3. 只读属性 readOnly=false 增删改,true 查询操作
  4. 超时属性 默认值 -1
  5. 异常属性 默认值

增删改操作:@Transactional
查询操作:@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)

在这里插入图片描述

基于标签的事务配置方式

前面我们讲的事务开发就是基于注解 @Transaction 的事务配置;

那么对应的,就肯定存在基于标签的事务配置:

<!--DataSourceTransactionManager-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

切入点 --> 作为事务操作
<tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager">
	事务属性
    <tx:attributes>
    	事务操作的方法 方法名 事务属性(隔离属性、传播属性)
    	也可以使用 * 号作为通配符
        <tx:method name="register" isolation="DEFAULT" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

组装切面
<aop:config>
    <aop:pointcut id="pc" expression="execution(* com.yusael.service.UserServiceImpl.register(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/>
</aop:config>

基于标签的事务配置在 实战 中的应用方式:

<tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager">
    <tx:attributes>
        <tx:method name="register"/>
        <tx:method name="modify*"/>
        * 号作为通配符
        编程时候, service中负责进行增删改操作的方法 都以 modify 开头
        						  查询操作 命名无所谓
        <tx:method name="*" propagation="SUPPORTS" read-only="true"/>
    </tx:attributes>
</tx:advice>

<aop:config>
	应用的过程中, 将 service 都放到 service 包下 方便指定切入点
    <aop:pointcut id="pc" expression="execution(* com.yusael.service..*.*(..))"/>
   	组装切面
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/>
</aop:config>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值