• Spring学习笔记(五):JDBCTemplate+事务管理


    1 概述

    Spring为开发者提供了JDBCTemplate,可以简化很多数据库操作相关的代码,本文主要介绍JDBCTemplate的使用以及事务管理功能。

    2 JDBC Template

    2.1 配置

    配置的话主要配置以下几项:

    • 数据源:org.springframework.jdbc.datasource.DriverManager.DataSource
    • 数据库驱动:com.cj.mysql.jdbc.Driver,这里采用的是MySQL 8,注意MySQL 5.7以下的驱动名字不同,另外若是其他数据库请对应修改
    • 数据库URLjdbc:mysql://localhost:3306/testMySQL默认的3306端口,数据库test
    • 数据库用户名
    • 数据库密码
    • JDBC模板:org.springframework.jdbc.core.jdbcTemplate

    参考配置如下:

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/test"/>
        <property name="username" value="test"/> 
        <property name="password" value="test"/>
    </bean>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <context:component-scan base-package="pers.dao"/>
    

    2.2 常用方法

    • int update(String sql,Object args[]):增/删/改操作,使用args设置其中的参数,返回更新的行数
    • List<T> query(String sql,RowMapper<T> rowMapper,Object []args):查询操作,rowMapper将结果集映射到用户自定义的类中

    2.3 示例

    2.3.1 依赖

    首先导入依赖:

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.2.9.RELEASE</version>
    </dependency>
    
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.21</version>
    </dependency>
    

    MySQL的版本请根据个人需要更改,或使用其他数据库的驱动。

    2.3.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 
           https://www.springframework.org/schema/context/spring-context.xsd">
    
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/test"/>
            <property name="username" value="test"/>
            <property name="password" value="test"/>
        </bean>
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <property name="dataSource" ref="dataSource"/>
        </bean>
        <context:component-scan base-package="pers.dao"/>
    </beans>
    

    2.3.3 实体类

    public class MyUser {
        private Integer id;
        private String uname;
        private String usex;
    }
    

    2.3.4 数据访问层

    添加@Repository以及@RequiredArgsConstructor

    @Repository
    @RequiredArgsConstructor(onConstructor = @__(@Autowired))
    public class TestDao {
        private final JdbcTemplate template;
    
        public int update(String sql,Object[] args)
        {
            return template.update(sql,args);
        }
    
        public List<MyUser> query(String sql, Object[] args)
        {
            RowMapper<MyUser> mapper = new BeanPropertyRowMapper<>(MyUser.class);
            return template.query(sql,mapper,args);
        }
    }
    

    因为直接使用@Autowired的话会提示不推荐:

    在这里插入图片描述

    所以利用了Lombok的注解@RequiredArgsConstructor,效果相当如下构造方法,只不过是简化了一点:

    @Autowired
    public TestDao(JdbcTemplate template)
    {
        this.template = template;
    }
    

    2.3.5 测试

    测试之前先建表:

    create table MyUser(
        id INT AUTO_INCREMENT PRIMARY KEY ,
        uname varchar(20),
        usex varchar(20)
    )
    

    测试类:

    public class Main {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            TestDao dao = (TestDao)context.getBean("testDao");
            String insertSql = "insert into MyUser(uname,usex) values(?,?)";
            String[] param1 = {"chenhengfa1","男"};
            String[] param2 = {"chenhengfa2","男"};
            String[] param3 = {"chenhengfa3","男"};
            String[] param4 = {"chenhengfa4","男"};
    
            dao.update(insertSql,param1);
            dao.update(insertSql,param2);
            dao.update(insertSql,param3);
            dao.update(insertSql,param4);
    
            String selectSql = "select * from MyUser";
            List<MyUser> list = dao.query(selectSql,null);
            for(MyUser mu:list)
            {
                System.out.println(mu);
            }
        }
    }
    

    输出:

    在这里插入图片描述

    如果出现异常或插入不成功等其他情况,请检查SQL语句是否编写正确,包括表名以及字段名。

    3 事务管理

    Spring中的事务管理有两种方法:

    • 编程式事务管理:代码中显式调用beginTransactioncommitrollback等就是编程式事务管理
    • 声明式事务管理:通过AOP实现,不需要通过编程方式管理事务,因此不需要再业务逻辑代码中掺杂事务处理的代码,开发更加简单,便于后期维护

    下面先来看一下编程式事务管理的实现。

    3.1 编程式事务管理

    编程式事务管理的配置又有两种方法:

    • 基于底层API
    • 基于TransactionTemplate

    需要的依赖如下:

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>5.2.9.RELEASE</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-expression</artifactId>
        <version>5.2.9.RELEASE</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.2.9.RELEASE</version>
    </dependency>
    
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.6</version>
        <scope>runtime</scope>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.2.9.RELEASE</version>
    </dependency>
    

    3.1.1 底层API实现

    根据PlatformTransactionManagerTransactionDefinitionTransactionStatus几个核心接口,通过编程方式进行事务管理,首先配置事务管理器:

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

    接着修改数据库访问类:

    @Repository
    @RequiredArgsConstructor(onConstructor = @__(@Autowired))
    public class TestDao {
        private final JdbcTemplate template;
        private final DataSourceTransactionManager manager;
    
        public int update(String sql,Object[] args)
        {
            return template.update(sql,args);
        }
    
        public List<MyUser> query(String sql,Object[] args)
        {
            RowMapper<MyUser> mapper = new BeanPropertyRowMapper<>(MyUser.class);
            return template.query(sql,mapper,args);
        }
    
        public void testTransaction()
        {
            TransactionDefinition definition = new DefaultTransactionDefinition();
            TransactionStatus status = manager.getTransaction(definition);
            String message = "执行成功,没有事务回滚";
    
            try
            {
                String sql1 = "delete from MyUser";
                String sql2 = "insert into MyUser(id,uname,usex) values(?,?,?)";
                Object [] param2 = {1,"张三","男"};
                template.update(sql1);
                template.update(sql2,param2);
                template.update(sql2,param2);
                manager.commit(status);
            }
            catch (Exception e)
            {
                e.printStackTrace();
                manager.rollback(status);
                message = "主键重复,事务回滚";
            }
            System.out.println(message);
        }
    }
    

    3.1.1.1 事务定义

    TransactionDefinition是事务定义,是一个接口:

    在这里插入图片描述

    主要定义了:

    • 事务隔离级别
    • 事务传播行为
    • 事务超时时间
    • 是否为只读事务

    DefaultTransactionDefinition就是上面属性的一些默认配置,比如:

    在这里插入图片描述

    也就是定义了:

    • 传播行为为0:也就是常量PROPAGATION_REQUIREDE,表示如果当前存在一个事务,则加入当前事务,如果不存在任何事务,就创建一个新事务
    • 隔离级别为-1:这个也是TransactionDefinition的默认参数,表示使用数据库的默认隔离级别,通常情况下为Read Committed
    • 超时为-1:默认设置不超时,如需要设置超时请调用setTimeout方法,比如如果设置为了60,那么相当于如果操作时间超过了60s,而且后面还涉及到CRUD操作,那么会抛出超时异常并回滚,如果超时操作的后面没有涉及到CRUD操作,那么不会回滚
    • 只读事务为false:默认为false,但是该变量不是表明“不能”进行修改等操作,而是一种暗示,如果不包含修改操作,那么JDBC驱动和数据库就有可能针对该事务进行一些特定的优化

    3.1.1.2 具体执行流程

    具体执行流程如下:

    • 定义事务:实例类为DefaultTransactionDefinition
    • 开启事务:通过getTransaction(TransactionDefinition)开启
    • 执行业务方法
    • 根据业务方法是否出现异常手动调用DataSourceTransactioncommit(TransactionStatus)进行提交
    • 出现异常调用rollback(TransactionStatus)进行回滚

    测试如下:

    在这里插入图片描述

    3.1.2 基于TransactionTemplate

    步骤:

    • 通过调用TransactionTemplateexecute实现
    • execute接受一个TransactionCallback接口参数
    • TransactionCallback定义了一个doInTransaction方法
    • 通常以匿名内部类的方式实现TransactionCallback接口,在其中的doInTransaction编写业务逻辑代码
    • doInTransaction有一个TransactionStatus的参数,可以调用setRollbackOnly进行回滚

    默认的回滚规则如下:

    • 如果抛出未检查异常或者手动调用setRollbackOnly,则回滚
    • 如果执行完成或抛出检查异常,则提交事务

    示例如下,首先编写配置文件对Bean进行注入:

    <!--事务管理器-->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--事务模板-->
    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="txManager"/>
    </bean>
    

    其次修改数据访问类,添加一个测试方法:

    public void testTransactionTemplate()
    {
        System.out.println(transactionTemplate.execute((TransactionCallback<Object>) transactionStatus -> {
            String deleteSql = "delete from MyUser";
            String insertSql = "insert into MyUser(id,uname,usex) values(?,?,?)";
            Object[] parm = {1, "张三", "男"};
            try {
                template.update(deleteSql);
                template.update(insertSql, parm);
                template.update(insertSql, parm);
            } catch (Exception e) {
                message = "主键重复,事务回滚";
                e.printStackTrace();
            }
            return message;
        }));
    }
    

    大部分代码与第一个例子类似就不解释了,结果也是因为主键重复出现异常,造成事务回滚:

    在这里插入图片描述

    3.2 声明式事务管理

    Spring声明式事务管理通过AOP实现,本质是在方法前后进行拦截,在目标方法开始之前创建或加入一个事务,执行目标方法完成之后根据执行情况提交或回滚事务。相比起编程式事务管理,声明式最大的优点就是不需要通过编程的方式管理事务,业务逻辑代码无需混杂事务代码,但是唯一不足的地方就是最细粒度只能作用到方法上,而不能做到代码块级别。

    实现方式有如下两种:

    • 基于XML实现
    • 基于@Transactional实现

    3.2.1 基于XML

    Spring提供了tx命令空间来配置事务:

    • <tx:advice>:配置事务通知,一般需要指定id以及transaction-manager
    • <tx:attributes>:配置多个<tx:method>指定执行事务的细节

    3.2.1.1 配置文件

    完整配置文件如下:

    <?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:aop="http://www.springframework.org/schema/aop"
           xmlns:cache="http://www.springframework.org/schema/cache"
           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
           https://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/cache
           http://www.springframework.org/schema/cache/spring-cache.xsd
           http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop.xsd"
    >
    
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/test"/>
            <property name="username" value="test"/>
            <property name="password" value="test"/>
        </bean>
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <property name="dataSource" ref="dataSource"/>
        </bean>
        <context:component-scan base-package="pers.dao"/>
        <!--事务管理器-->
        <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"/>
        </bean>
    
        <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
            <property name="transactionManager" ref="txManager"/>
        </bean>
    
    	<!--声明式事务-->
        <tx:advice id="myAdvice" transaction-manager="txManager">
            <tx:attributes>
            	<!--任意方法-->
                <tx:method name="*" />
            </tx:attributes>
        </tx:advice>
        <!--aop配置,具体可以看笔者之前的文章-->
        <aop:config>
        	<!--定义切点,执行testXMLTranscation()时进行增强-->
            <aop:pointcut id="txPointCut" expression="execution(* pers.dao.TestDao.testXMLTransaction())"/>
            <!--切面-->
            <aop:advisor advice-ref="myAdvice" pointcut-ref="txPointCut"/>
        </aop:config>
    </beans>
    

    3.2.1.2 测试

    测试方法如下:

    public void testXMLTransaction()
    {
        String deleteSql = "delete from MyUser";
        String saveSql = "insert into MyUser(id,uname,usex) values(?,?,?)";
        Object [] parm = {1,"张三","男"};
        template.update(deleteSql);
        template.update(saveSql,parm);
        template.update(saveSql,parm);
    }
    

    运行结果:

    在这里插入图片描述

    可以看到提示主键重复了。

    3.2.2 基于@Transactional

    @Transactional一般作用于类上,使得该类所有public方法都具有该类型的事务属性。下面创建一个示例。

    3.2.2.1 配置文件

    将上一个例子中的<aop:config>以及<tx:advice>注释掉,同时添加:

    <!--事务管理的注解驱动器-->
    <tx:annotation-driven transaction-manager="txManager"/>
    

    在这里插入图片描述

    3.2.2.2 测试

    测试方法与上一个例子一致,结果也是如此:

    在这里插入图片描述

    4 参考源码

    Java版:

    Kotlin版:

  • 相关阅读:
    leetcode69 X的平方根的几种解法
    记录一次安装OpenGL的漫长过程
    Java序列化和反序列化
    volatile和synchronized关键字
    netty中的传输
    SpringBoot集成netty实现客户端服务端交互和做一个简单的IM
    netty入坑第一步:了解netty和编写简单的Echo服务器和客户端
    java几种垃圾收集方法和垃圾收集器
    SpringCloud-7-Config
    SpringCloud-6-Zuul
  • 原文地址:https://www.cnblogs.com/6b7b5fc3/p/13741163.html
Copyright © 2020-2023  润新知