[Re] Spring-3(JdbcTemplate)

Spring 提供了 JdbcTemplate 以便捷地操作 DB。

1. CRUD 测试

1.1 导包

1.2 配置文件

<!-- 
DAO 层组件自动装配 JdbcTemplate:
    @Repository
    public class EmpDao {
        @Autowired
        JdbcTemplate jdbcTemplate;
    }
-->
<context:component-scan base-package="cn.edu.nuist"></context:component-scan>

<!-- 引用外部属性文件;"classpath:" 固定写法,引入类路径下资源 -->
<context:property-placeholder location="classpath:dbconfig.properties"/>

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="user" value="${jdbc.user}"></property>
    <property name="password" value="${jdbc.password}"></property>
    <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
    <property name="driverClass" value="${jdbc.driverClass}"></property>
</bean>

<!-- Spring 提供了一个类 JdbcTemplate,使用它来操作 DB -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>

<!-- 配置一个具有 ‘具名参数’ 功能的 JdbcTemplate -->
<bean id="namedParameterJdbcTemplate"
        class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
    <constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>

1.3 测试

public class TestJDBC {
    ApplicationContext ioc = new 
            ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
    NamedParameterJdbcTemplate namedParamJdbcTemplate = 
            ioc.getBean(NamedParameterJdbcTemplate.class);

    // 8. 创建 EmpDao,自动装配 JdbcTemplate 对象
    @Test
    public void testEmpDao() {
        EmpDao empDao = ioc.getBean(EmpDao.class);
        Employee emp = new Employee();
        emp.setEmpName("吴九");
        emp.setSalary(5000);
        empDao.saveEmp(emp);
        empDao.saveEmp(emp);
    }

    // 7. 以SqlParameterSource形式传入参数值
    public void testSqlParameterSource() {
        String sql = "INSERT INTO employees(emp_name, salary) VALUES(:empName, :salary)";
        Employee emp = new Employee();
        emp.setEmpName("崔八");
        emp.setSalary(5000);
        // SqlParameterSource BeanPropertySqlParameterSource
        int update = namedParamJdbcTemplate.update(sql
                , new BeanPropertySqlParameterSource(emp));
        System.out.println(update);
    }

    /*
    6. 使用带有具名参数的 SQL 语句插入一条员工记录,并以 Map 形式传入参数值
        占位符参数:? 的顺序不能乱,传参的时候要注意
        具名参数:具有名字的参数,参数不是占位符了,而是一个变量名
            语法格式 [:参数名]
            Spring 有一个支持具名参数功能的JdbcTemplate: NamedParameterJdbcTemplate
     */
    public void insertWithMap() {
        String sql = "INSERT INTO employees(emp_name, salary) VALUES(:empName, :salary)";
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("empName", "田七");
        paramMap.put("salary", 5000);
        int update = namedParamJdbcTemplate.update(sql, paramMap);
        System.out.println(update);
    }

    // 5. 更新数据
    public void testUpdate() {
        String sql = "UPDATE employees SET salary = ? WHERE emp_id = ?";
        int update = jdbcTemplate.update(sql, 4000, 4);
        System.out.println(update);
    }

    // 4. 批量插入数据
    public void testBatchAdd() {
        String sql = "INSERT INTO employees(emp_name, salary) VALUES(?, ?)";
        // List<Object[]>
        // List.size: SQL 语句要执行的次数
        // Object[]: 每次执行要用的参数
        List<Object[]> batchArgs = new ArrayList<>();
        batchArgs.add(new Object[] {"张三", 3000});
        batchArgs.add(new Object[] {"李四", 3000});
        batchArgs.add(new Object[] {"王五", 3000});
        batchArgs.add(new Object[] {"赵六", 3000});
        int[] updates = jdbcTemplate.batchUpdate(sql, batchArgs);
        System.out.println(Arrays.toString(updates));
    }

    // 3. 查询单个数据
    public void queryMaxSalary() {
        String sql = "SELECT MAX(salary) FROM employees";
        int maxSalary = jdbcTemplate.queryForObject(sql, Integer.class);
        System.out.println(maxSalary);
    }

    /*
     * JavaBean 需要和 DB 中表的字段名一致,否则无法完成封装
     * JdbcTemplate 在方法级别进行了区分
     *  > 查询单个对象:template.queryForObject(),如果查询不到就会抛异常
     *  > 查询集合:template.query()
     */

    // 2. 查询多条记录,封装到 List
    public void queryMore() {
        String sql = "SELECT emp_id empId, emp_name empName, salary"
                + " FROM employees WHERE salary > ?";
        // 封装 List
        List<Employee> emps = jdbcTemplate.query(sql
                , new BeanPropertyRowMapper<>(Employee.class), 3000);
        System.out.println(emps);
    }

    // 1. 查询 1 条记录,封装到 JavaBean
    public void queryOne() {
        String sql = "SELECT emp_id empId, emp_name empName, salary"
            + " FROM employees WHERE emp_id = ?";
        Employee emp = jdbcTemplate.queryForObject(sql
                , new BeanPropertyRowMapper<>(Employee.class), 40);
        System.out.println(emp);
    }
}

2. 声明式事务

事务管理代码的固定模式作为一种横切关注点,可以通过 AOP 方法模块化,进而借助 Spring AOP 框架实现声明式事务管理。

这个事务管理器就可以在目标方法运行前后进行事务控制(事务切面)。我们目前使用 DataSourceTransactionManager 即可。

2.1 配置文件

<!-- 导入外部配置文件 -->
<context:property-placeholder location="classpath:dbconfig.properties"/>

<!-- 配数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="${jdbc.driverClass}"/>
    <property name="jdbcUrl" value="${jdbc.jdbcUrl}"/>
    <property name="user" value="${jdbc.user}"/>
    <property name="password" value="${jdbc.password}"/>
    <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
    <property name="minPoolSize" value="${jdbc.minPoolSize}"></property>
</bean>

<!-- 事务控制-->
<!-- 1. 配置事务管理器让其进行事务管理 -->
<bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!-- 控制住数据源 (控制事务实际就是控制住Connection) -->
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 2. 开启基于注解的事务控制模式(依赖 tx 名称空间), 默认 transactionManager -->
<tx:annotation-driven transaction-manager="transactionManager"/>

<!-- 3. 给事务方法加 @Transactional → 环绕通知Around -->

2.2 @Transactional

2.2.1 propagation

事务的传播行为(事务的传播 + 事务的行为),如果有多个事务进行嵌套运行,子事务是否和大事务共用一个事务(使用同一条连接)。

  • 如果是 REQUIRED,则子事务的属性都是继承自大事务。
  • REQUIRED 是将之前事务用的 Connection 传递过来
  • REQUIRES_NEW 直接使用新的 Connection

2.2.2 isolation

事务的隔离级别。

  • READ_UNCOMMITTED
  • READ_COMMITTED
  • REPEATABLE_READ
  • SERIALIZABLE

2.2.3 timeout

  • 超时属性
  • 事务超出指定时长(秒为单位) 后自动终止并回滚

2.2.4 readOnly

  • 默认 false
  • 可以进行事务优化
  • 能加快查询速度,不用管事务那堆操作了
  • 如果设置为 true 后,做非查询操作,会抛异常

2.2.5 rollback 相关

  • 异常分类
    • 运行时异常(非检查异常):可以不用处理,默认都回滚
    • 编译时异常(检查异常):默认不回滚
  • 回滚相关属性
    • rollbackFor:出现哪些异常,事务可以不回滚
    • rollbackForClassName
    • noRollbackFor:原来不回滚的异常,指定让其回滚
    • noRollbackForClassName

3. 基于 XML 配置的事务

  1. 依赖 tx、aop 名称空间
  2. <aop:pointcut> 切入点表达式
  3. <tx:advice> 事务通知,类比注解式中的切面类 (事务切面按照切入点表达式去切入事务方法)
  4. <aop:advisor> 关联 [切入点表达式] 和 [事务通知]

<aop:config>
    <!-- 切入点表达式 -->
    <aop:pointcut id="txPoint" expression="execution(* cn.edu.nuist.service.*.*(..))"/>
    <!-- 事务建议:关联 [切入点表达式] 和 [事务通知] -->
    <aop:advisor pointcut-ref="txPoint" advice-ref="myAdvice"/>
</aop:config>

<!-- 事务通知 // 关联 [事务管理器] -->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
    <!-- 事务属性 -->
    <tx:attributes>
        <!-- 指明哪些方法是事务方法:
        切入点表达式只是说,事务管理器要切入这些方法,至于方法的详细
        配置(传播行为、隔离级别、回滚异常 ...),还需要使用 tx:method
        -->
        <tx:method name="*"/>
        <tx:method name="checkout" propagation="REQUIRED" timeout="-1"/>
        <tx:method name="get*" read-only="true"/>
    </tx:attributes>
</tx:advice>

<!-- 都用;重要的用配置,不重要的用注解 -->
原文地址:https://www.cnblogs.com/liujiaqi1101/p/13672349.html