spring_53-66 Dao编程

148 阅读6分钟

53、spring整合jdbc

SpringFramework 有给我们提供一个数据源的模型类,它叫 DriverManagerDataSource

但是!DriverManagerDataSource 的底层就是 DriverManager.getConnection() 的操作,并没有连接池的设计,所以性能比较差,真正的开发中可千万不要用它!

spring整合jdbc的方法

将datasource传入jdbcTeamplate对象内,有编程式、xml式两种:

 public static void main(String[] args) throws Exception {
     DriverManagerDataSource dataSource = new DriverManagerDataSource();
     dataSource.setDriverClassName("com.mysql.jdbc.Driver");
     dataSource.setUrl("jdbc:mysql://localhost:3306/spring-dao?characterEncoding=utf8");
     dataSource.setUsername("root");
     dataSource.setPassword("123456");
     
     JdbcTemplate jdbcTemplate = new JdbcTemplate();
     jdbcTemplate.setDataSource(dataSource);
 }
 <?xml version="1.0" encoding="UTF-8"?>
 <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="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
         <property name="dataSource" ref="dataSource"/>
     </bean>
 ​
     <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
         <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
         <property name="url" value="jdbc:mysql://localhost:3306/spring-dao?characterEncoding=utf8"/>
         <property name="username" value="root"/>
         <property name="password" value="123456"/>
     </bean>
 </beans>

jdbcTemplate 的 crud

使用 xml 方式注册IOC容器:

 ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("jdbc/spring-jdbc.xml");
 JdbcTemplate jdbcTemplate = ctx.getBean(JdbcTemplate.class);
  • insert 操作

    除了execute操作之外,还可以使用 update 命令,有返回值 0/1:

     jdbcTemplate.update("insert into tbl_user (name, tel) values (?, ?)", "heihei", "200");
    
  • update 操作

    当然是 update 命令啦:

     jdbcTemplate.update("update tbl_user set tel = ? where name = ?", "54321", "heihei");
    
  • delete 操作

    还是 update 命令:

     int row = jdbcTemplate.update("delete from tbl_user where name = ?", "heihei");
    
  • select 操作

    首选方法:

    query(String sql, RowMapper<T> rowMapper, Object... args)

    其中, RowMapper 接口 会将 ResultSet (query结果)的一行数据封装为一个指定的类型(泛型中的类型)。一般选择 new 一个 **BeanPropertyRowMapper**实现类 :

     // 查全表
     List<User> userList = jdbcTemplate.query("select * from tbl_user", new BeanPropertyRowMapper<>(User.class));
     ​
     // 条件查询 ———— 查出表中 id 编号大于2的数据
     List<User> userList = jdbcTemplate.query("select * from tbl_user where id > ?", new BeanPropertyRowMapper<>(User.class), 2);
     ​
     ​
     // 某条件下,取多个返回结果中的单个
     List<User> userList = jdbcTemplate.query("select * from tbl_user where id = ?", new BeanPropertyRowMapper<>(User.class), 2);
     User user = userList.size() > 0 ? userList.get(0) : null;
    

    Tip:这个 userList 不需要判空吗? 答案是不需要, JdbcTemplate 底层封装结果集的逻辑:无论查出来有没有数据结果返回, List 都先要初始化,它不可能为 null ,所以可以用它的 size 去做判断。

    queryForObject() ,它可以直接指定返回数据要封装的类型。

     // 查数量
     Long count = jdbcTemplate.queryForObject("select count(id) from tbl_user", Long.class);
    

54、JdbcTemplate在Dao层的使用方式

JdbcTemplate移入Dao层

上一章学习了JdbcTemplate的使用方法,但还没有集成到三层架构中。而数据层的访问应该要写到 Dao 层中。

之前在学习 JavaWeb 基础时,与数据库交互的 Dao 层,是使用 jdbc 的方式 ——> 整合了 SpringFramework 的 jdbc 之后,就应该使用 JdbcTemplate 了。

本章代码采用注解驱动的方式

image-20210817112808169.png

其中:

  • UserDaoImpl 中使用 @Autowired 注入了 JdbcTemplate;

     @Repository
     public class UserDaoImpl implements UserDao {
     ​
         @Autowired
         private JdbcTemplate jdbcTemplate;
     }
    
  • JdbcConfiguration 中注册了dataSourcejdbcTemplate 两个Bean,分别返回对应的对象:

     @Bean
     public DataSource dataSource(){
         DriverManagerDataSource dataSource = new DriverManagerDataSource();
         dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
         dataSource.setUrl("jdbc:mysql://localhost:3306/spring-dao?characterEncoding=utf8");
         dataSource.setUsername("root");
         dataSource.setPassword("131313");
         return dataSource;
     }
     ​
     @Bean
     public JdbcTemplate jdbcTemplate(){
         return new JdbcTemplate(dataSource());
     }
    

JdbcDaoSupport

上面 UserDaoImpl问题 —— 若有两个UserDao的实现类,因为每一个实现类中不可避免的需要注入JdbcTemplate,会造成重复注入的问题。

解决方案 ——

  1. 解决jdbcTemplate重复注册 :编写一个父类,声明自动注入的JdbcTemplate 为private,让子类用 get/set 方法 获取/设置。
  2. 解决需手动在配置类中注册jdbcTemplate : JdbcTemplate 是依赖 DataSource 的,可以把 DataSource 传递给 父类,让它自己创建 JdbcTemplate

上面的解决方案其实就是 JdbcDaoSupport抽象类 的实现思路,我们使用时直接继承它就行了。核心代码:

 public abstract class JdbcDaoSupport extends DaoSupport {
 ​
     @Nullable
     private JdbcTemplate jdbcTemplate;
 ​
     public final void setDataSource(DataSource dataSource) {
         if (this.jdbcTemplate == null || dataSource != this.jdbcTemplate.getDataSource()) {
             this.jdbcTemplate = createJdbcTemplate(dataSource);
             initTemplateConfig();
         }
     }

但使用 JdbcDaoSupport 还需借助后置处理器,另外覆写一个方法:

 public class JdbcDaoSupportPostProcessor implements BeanPostProcessor {
     
     @Autowired
     private DataSource dataSource;
     
     @Override
     public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
         if (bean instanceof JdbcDaoSupport) {
             JdbcDaoSupport daoSupport = (JdbcDaoSupport) bean;
             daoSupport.setDataSource(dataSource);
         }
         return bean;
     }
 }

55、事务回顾 | 原生 JDBC 事务

事务

  • 事务的四大特性: 原子性(事务的整体)、持久性(数据的完整)、隔离性(并发的隔离)、持久性(操作的结果)
  • 事务并发操作中三种问题: 脏读、不可重复读、幻读
  • 事物的隔离级别(由并发问题引出的): 读未提交、读已提交、可重复读、可串行化

原生jdbc事务

在日常开发中会遇到一些特殊的场景,它可能需要分段执行,如果出现异常后不需要全部回滚,这种情况就需要 jdbc 事务中的 保存点 了。

  • 常用操作:

    • 获取connection: connection = dataSource.getConnection();

    • 开启事务(关闭自动提交): connection.setAutoCommit(false);

    • 预定义statement:

      PreparedStatement statement = connection.prepareStatement("sql语句");

    • statement真正执行: statement.executeUpdate();

    • 事务提交: connection.commit();

    • 事务回滚: connection.rollback();

    • 关闭connection: connection.close();

  • 保存点:

    • 设置保存点: savepoint = connection.setSavepoint();
    • 回滚到保存点: connection.rollback(savepoint);

56、编程式事务

事务应该放在业务层控制,所以接下来的代码中会有 Service 和 Dao 两部分。

注解含义
@Component最普通的组件,可以被注入到spring容器进行管理
@Repository作用于持久层,@Repository注解类作为DAO对象,可以直接对数据库进行操作
@Service作用于业务逻辑层
@Controller作用于表现层(spring-mvc的注解)

pipeline

  • 整体架构:

image-20210820094808929.png

其中,*Dao 要标注 `@Repository` 注解,Service 要标注 `@Service` 注解。*
  • 注册相关组件

    DataSourceTransactionManager :事务管理器,它负责控制事务

    TransactionTemplate :事务模板,使用它可以完成编程式事务

     <bean id="transactionManager" 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="transactionManager"/>
     </bean>
    

    他们的关系为:transactionTemplate 包含 transactionManager 包含 dataSource

  • service使用事务模板

    • 在 Service 中注入事务模板 TransactionTemplate;

    • 事务模板的使用方法是 transactionTemplate.execute()自定义 service 中每一个事务方法都要使用 execute() 方法进行事务的提交,但其中一般只实现事务逻辑上的操作,与数据库的交互( sql语句的执行 )则由Dao类内实现的相应方法完成

      • 这个 execute 方法中需要传入一个 TransactionCallback 类型的对象,而这个 TransactionCallback 本身是一个函数式接口,所以可以lambda编程。

         @FunctionalInterface
         public interface TransactionCallback<T> {
             T doInTransaction(TransactionStatus status);
         }
        
       transactionTemplate.execute(status ->{
           userDao.save(user);
           int i = 1 / 0;
           List<User> userList = userDao.findAll();
           System.out.println(userList);
           return null;
       });
      
  • TransactionCallback 的优化

    execute() 源码如下:

     public <T> T execute(TransactionCallback<T> action) throws TransactionException {
         // ......
             T result;
             try {
                 // 此处是lambda表达式的返回值
                 result = action.doInTransaction(status);
             } 
             // ......
             // 返回出去了
             return result;
         }
     }
    

    为什么上面的 lambda 要返回null?因为lambda返回值等于execute方法的返回值。

    除了可以使用 TransactionCallback 接口的 lambda 实现之外, TransactionCallback 接口加了一个抽象类 TransactionCallbackWithoutResult :只要覆写 doInTransaction 方法即可,方法内部帮我们返回了 null,其他不变。

DataSourceTransactionManager

它是基于数据源的事务管理器。它实现的根接口 PlatformTransactionManager 有定义 commitrollback 方法:

 public interface PlatformTransactionManager extends TransactionManager {
     TransactionStatus getTransaction(TransactionDefinition definition)
             throws TransactionException;
     void commit(TransactionStatus status) throws TransactionException;
     void rollback(TransactionStatus status) throws TransactionException;
 }

这个 commitrollback 方法要传入一个 TransactionStatus 的参数,后面第 60 章 - Spring 事务控制模型再讲解。

TransactionTemplate

事务模板,它与前面咱学的 JdbcTemplate 在设计上是类似的,都是提供一个简单的模板来完成平时比较复杂的工作JdbcTemplate 解决的是不需要再写那些复杂的 StatementResultSet 等等)。

TransactionTemplate 本质上是一个 TransactionDefinition它的核心方法是来自 TransactionOperations 接口定义的 execute 方法:(注意下面 else 块中的注释)

 @Override
 @Nullable
 public <T> T execute(TransactionCallback<T> action) throws TransactionException {
     Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
 ​
     if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
         return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
     }
     else {
         TransactionStatus status = this.transactionManager.getTransaction(this);
         T result;
         try {
             result = action.doInTransaction(status);
         }
         catch (RuntimeException | Error ex) {
             // 业务代码出现异常,回滚事务
             rollbackOnException(status, ex);
             throw ex;
         }
         catch (Throwable ex) {
             // 业务代码出现异常,回滚事务
             rollbackOnException(status, ex);
             throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
         }
         // try块没有出现异常,业务代码执行成功,提交事务
         this.transactionManager.commit(status);
         return result;
     }
 }

它提交和回滚事务的动作,就是拿的 TransactionManager 执行的 commitrollback 方法。

57、声明式事务

编程式事务中,每个 Service 中都要注入 TransactionTemplate ;编写的所有方法都要使用 transactionTemplate.execute 方法完成事务的逻辑操作。太麻烦了!

\