Spring JDBCTemplate

jdbcTemplate 是spring框架中提供的一个模板对象,是对原始繁琐的jdbc API 对象的简单封装。 它和Dbutils是类似的都是对jdbc的封装。

下面看jdbcTemplate的使用案例

  1. 导入依赖

    1. <!-- spring jdbc template依赖 -->
    2. <dependency>
    3. <groupId>org.springframework</groupId>
    4. <artifactId>spring-jdbc</artifactId>
    5. <version>5.1.5.RELEASE</version>
    6. </dependency>
    7. <!-- spring 事务管理依赖 -->
    8. <dependency>
    9. <groupId>org.springframework</groupId>
    10. <artifactId>spring-tx</artifactId>
    11. <version>5.1.5.RELEASE</version>
    12. </dependency>
  2. DAO 层的代码实现 ```java @Repository(“accountDao”) public class AccountDaoImpl implements AccountDao {

    @Autowired //获取jdbcTemplate实例 private JdbcTemplate jdbcTemplate;

    @Override public List findAll() {

     String sql = "select * from account";
     List<Account> accounts = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Account.class));
     return accounts;
    

    }

    @Override public Account findById(Integer id) {

     String sql = "select * from account where id = ?";
     Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Account.class), id);
     return account;
    

    }

    @Override public void save(Account account) {

     String sql = "insert into account(name,money) values(?,?)";
     int update = jdbcTemplate.update(sql, account.getName(), account.getName());
    

    }

    @Override public void update(Account account) {

     String sql = "update account set money=? where name = ?";
     int update = jdbcTemplate.update(sql, account.getMoney(), account.getName());
    

    }

    @Override public void delete(Integer id) {

     String sql = "delete from account where id = ?";
     int update = jdbcTemplate.update(sql, id);
    

    } }


3. Service层的代码实现
```java
@Service("accountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Override
    public List<Account> findAll() {
        return accountDao.findAll();
    }

    @Override
    public Account findById(Integer id) {
        return accountDao.findById(id);
    }

    @Override
    public void save(Account account) {
        accountDao.save(account);
    }

    @Override
    public void update(Account account) {
        accountDao.update(account);
    }

    @Override
    public void delete(Integer id) {
        accountDao.delete(id);
    }
}
  1. applicationContext.xml核心配置文件 ```xml <?xml version=”1.0” encoding=”UTF-8” ?>

数据库信息配置文件:`jdbc.properties`
```xml
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_db?characterEncoding=utf-8
jdbc.username=root
jdbc.password=123456

基于JdbcTemplate实现转账操作,但是两个SQL执行没有在同一个事务中执行,出现异常会存在问题。

    @Override
    public void transfer(String outUser, String inUser, Double money) {
        accountDao.out(outUser, money);
//        int i = 1/0;
        accountDao.in(inUser, money);
    }

下面如我们来看spring的事务管理整合jdbcTemplate实现事务的管理。

Spring声明式事务

Spring的事务控制分为编程式事务控制和声明式事务控制 编程式:直接把事务的代码和业务代码耦合在一起,在实际开发中不会用到的 声明式:采用配置的方式来实现事务控制,使用的就是AOP思想。

编程式事务(了解)

PlatformTransactionManager

是spring的事务管理器,里面提供了我们常用的操作事务方法,和我们之前将的AOP的TransactionManager类似

image.png
DAO层技术

  • 是jdbcTemplate或mybatis时:

DataSourceTransactionManager 的实现类

  • hibernate时:

HibernateTransactionManager的实现类

  • JPA时:

JpaTransactionManager

TransactionDefinition

TransactionDefinition 接口提供事务的定义信息(事务隔离级别、事务传播行为等等)

image.png1.

  1. 事务隔离级别

主要解决事务并发产生的问题,如:脏读、不可重复读和幻读

  • IOSLATION_DEFAULT 使用数据库默认级别
  • IOSLATION_READ_UNCOMMITTED 读未提交
  • IOSLATION_READ_COMMITTED 读已提交
  • IOSLATION_REPEATABLE_READ 可重复读
  • IOSLATION_SERIALIZABLE 串行化
  1. 事务传播行为

    事务传播行为指的就是当一个业务被另外一个业务方法调用时,如何进行事务控制。

参数 说明
REQUIRED 如果当前没有事务,就新建一个事物,如果存在一个事物,加入到这个事务中。一般的选择(默认值)
领导没饭吃,我有钱,我会自己买了自己吃。领导有的吃,分给我一起吃。
当前被调用的方法,必须进行事务控制
SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
领导没饭吃,那我也没饭吃。
领导有饭吃,那我也有饭吃。
当前被调用的方法,有没有事务都可以执行
MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常
REQUERS_NEW 新建事务,如果当前在事务中,把当前事务挂起
NOT_SUPPORTS 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
NEVER 以非事务方式运行,如果当前存在事务,抛出异常
NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务则执行和REQUIRED类似的操作

TransactionStatus

提供的是事物具体的运行状态

image.png
可以理解三者的关系:事务管理器通过读取事务定义参数进行事务管理,然后会产生一系列的事务状态

配置文件配置事务管理器:
image.png
业务层的使用事务:
image.png

基于XML的声明式事务控制【重点】

直接上代码,通过使用spring声明式事务控制转账业务。

  • 核心业务代码(目标对象 - 切入点是谁)
  • 事务增强代码(spring 已提供事务管理器-通知是谁)
  • 切面配置(切面如何配置?)
  1. spring核心配置文件,引入tx命名空间和约束路径

    <?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:tx="http://www.springframework.org/schema/tx"
        xsi:schemaLocation="
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
         http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/aop
         http://www.springframework.org/schema/aop/spring-aop.xsd
         http://www.springframework.org/schema/tx
         http://www.springframework.org/schema/tx/spring-tx.xsd">
    </beans>
    
  2. 事务管理器通知配置

在上述的编程式事务管理中,我们知道DataSourceTransactionManager(DAO层技术为JdbcTemplate或Mybatis的时候)是PlatformTransactionManager的实现类,创建事务管理器对象。

    <!-- 事务管理器对象 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property ref="dataSource" name="dataSource"/>
    </bean>
  1. 事务管理器AOP配置

     <!-- 通知增强 -->
     <tx:advice id="txAdvice" transaction-manager="transactionManager">
         <!-- 定义事务的一些属性 -->
         <tx:attributes>
             <!-- name="*" 当前的任意名称的方法都走默认配置 -->
             <tx:method name="*"/>
         </tx:attributes>
     </tx:advice>
    
     <!-- aop配置 -->
     <aop:config>
    
         <!-- 抽取切点表达式 -->
         <aop:pointcut id="txPointcut" expression="execution(* com.prim.service.impl.AccountServiceImpl.*(..))"/>
    
         <!-- 事务的配置需要使用advisor 定义切面:通知+切点  -->
         <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
     </aop:config>
    
  2. 测试

在业务层抛出一个异常:

    @Override
    public void transfer(String outUser, String inUser, Double money) {
        accountDao.out(outUser, money);
        int i = 1/0;
        accountDao.in(inUser, money);
    }

测试代码如下:查看数据库表是否发生变化,如果没有变化则说明配置声明式事务成功

    @Test
    public void testTransfer() {
        accountService.transfer("tom", "jerry", 100d);
    }

事务配置参数详解

        <!-- 定义事务的一些属性 -->
        <tx:attributes>
            <!-- name="*" 切点方法名称 当前的任意名称的方法都走默认配置
                isolation:事务隔离级别 REPEATABLE_READ - mysql的默认隔离级别
                propagation:事务传播行为 REQUIRED
                read-only:是否只读
                timeout:超时时间 -1表示没有超时时间
             -->
            <tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"
                       timeout="-1"/>
           <!-- 常用配置 -->
            <tx:method name="save*" propagation="REQUIRED"/>
            <tx:method name="delete*" propagation="REQUIRED"/>
            <tx:method name="update*" propagation="REQUIRED"/>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>

基于注解的声明式事务控制【重点】

  1. service层添加事务注解

     @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ, timeout = -1, readOnly = false)
     @Override
     public void transfer(String outUser, String inUser, Double money) {
         accountDao.out(outUser, money);
         int i = 1 / 0;
         accountDao.in(inUser, money);
     }
    
  2. spring核心配置文件,开启事务注解扫描

     <!-- 事务管理器对象 事务管理器 必须存在的-->
     <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
         <property ref="dataSource" name="dataSource"/>
     </bean>
    
     <!-- 开启事务注解扫描 -->
     <tx:annotation-driven/>
    

需要注意:注意在类上添加:@Transactional注解 会对该类的所有方法进行事务控制


@Service("accountService")
@Transactional //注意在类上添加:@Transactional注解 会对该类的所有方法进行事务控制
public class AccountServiceImpl implements AccountService {
}

纯注解的配置:
Spring核心配置类:

@Configuration //变成核心配置类
@ComponentScan("com.prim") //注解扫描
@Import(DataSourceConfig.class)
@EnableTransactionManagement //事务注解驱动
public class SpringConfig {
    @Bean //把jdbcTemplate对象放到IOC容器中
    public JdbcTemplate getJdbcTemplate(@Autowired DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        return jdbcTemplate;
    }

    @Bean
    public PlatformTransactionManager getTransactionManager(@Autowired DataSource dataSource) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}

数据源配置类:

@PropertySource("classpath:jdbc.properties")
public class DataSourceConfig {
    @Value("${jdbc.driverClassName}")
    private String driverClassName;

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    @Bean //把返回值对象放到IOC容器中
    public DataSource getDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
}
  • 注意:一定要配置事务管理器配置(不管是注解还是xml方法)
  • 事务通知的配置(@Transaction注解配置)
  • 事务注解驱动配置(xml: 注解的方式:@EnableTransactionManagement)

    Spring 集成web环境

    应用上下文对象是通过 new ClasspathXmlApplicationContext(spring配置文件) 方式获取的,但 是每次从容器中获得Bean时都要编写 new ClasspathXmlApplicationContext(spring配置文件) , 这样的弊端是配置文件加载多次,应用上下文对象创建多次.

解决思路分析:
在Web项目中,可以使用ServletContextListener监听Web应用的启动,我们可以在Web应用启动 时,就加载Spring的配置文件,创建应用上下文对象ApplicationContext,在将其存储到最大的域servletContext域中,这样就可以在任意位置从域中获得应用上下文ApplicationContext对象了

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>
    </dependencies>

配置spring核心文件:

<?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:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">

    <bean id="account" class="com.prim.domain.Account">
        <property name="id" value="1"/>
        <property name="name" value="jake"/>
        <property name="money" value="100"/>
    </bean>

</beans>

配置ContextLoaderListener监听器

<!--全局参数--> 
<context-param>
    <param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>• </context-param>
<!--Spring的监听器-->
<listener>
    <listener-class>
        org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener>

获取applicationContext对象

        ApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());
        Account account = (Account) webApplicationContext.getBean("account");
        System.out.println(account);