一、JdbcTemplate 工具
JdbcTemplate 类是 Spring 框架提供一个用于操作数据库的模板类,JdbcTemplate 类支持声明式事务管理。该类提供如下方法来执行数据库操作。
1、queryForObject 查询单个对象
queryForObject(String sql,RowMapper mapper,Object[] args)
2、query 查询集合
List query(String sql,RowMapper mapper,Object[] args)
3、update 增加、删除、修改
int update(String sql, Object[] args)
4、batchUpdate 批量增加、删除、修改
int[] batchUpdate(String sql, List list)
package com.qlu.pojo;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
@Data
@Accessors(chain = true)
public class Book {
private Long id;
private String bookName;
private String bookAuthor;
private Date createTime;
private Date updateTime;
}
相同包的放到一起了,大家当个工具就行,毕竟都用框架了,集成其他的比这方便。
package com.qlu.service;
import com.qlu.pojo.Book;
public interface BookService {
void add(Book book);
void add(Integer value);
void delete();
void update(Book book);
void find();
}
package com.qlu.service.impl;
import com.qlu.pojo.Book;
import com.qlu.service.BookService;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
@Service("bookService")
public class BookServiceImpl implements BookService {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public void add(Book book) {
System.out.println("添加图书");
String sql = "insert into book (book_name,book_author,create_time,update_time) " +
"values(?,?,?,?)";
Object[] args = {book.getBookName(),book.getBookAuthor(),book.getCreateTime(),book.getUpdateTime()};
int result = jdbcTemplate.update(sql, args);
System.out.println(result>0?"成功":"失败");
}
@Override
public void add(Integer value) {
System.out.println("有参数的add方法 添加图书");
}
@Override
public void delete() {
System.out.println("删除图书");
}
@Override
public void update(Book book) {
System.out.println("修改图书");
String sql = "update book set book_name =?,book_author=?,update_time=? where id = ?";
Object[] args = {book.getBookName(),book.getBookAuthor(),book.getUpdateTime(),book.getId()};
int result = jdbcTemplate.update(sql, args);
System.out.println(result>0?"成功":"失败");
}
@Override
public void find() {
System.out.println("查找图书");
}
}
就用了一个 @Service 注解,其他的 bean 都是直接配置在 xml 文件中的。
标记
<?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:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="bookService" class="com.qlu.service.impl.BookServiceImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<!-- 配置 Spring 的事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置 C3P0 数据库链接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl"
value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=UTF-8&useUnicode=true"/>
<property name="user" value="root"/>
<property name="password" value="a.miracle"/>
<property name="maxPoolSize" value="50"/>
<property name="minPoolSize" value="20"/>
<property name="initialPoolSize" value="20"/>
<property name="maxIdleTime" value="200"/>
</bean>
<!--配置jdbc模板工具类-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
因为使用到了数据库连接池的配置,所以需要以下东西
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
到这儿就差不多了,反正我不想写了。
二、Spring 事务
此部分用到了上面的 Jdbc模板工具类
(一)事务概述
事务是恢复和并发控制的基本单位。即一组操作要么全都不做,要不就全都做。事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
1.原子性:一个事务不可分割,里面的操作只能选择全做或者全不做。
2.一致性:即事务是把数据库从一个一致的状态变到另一个一致的状态。
3.隔离性:事务的执行不能相互干扰。
4.持久性:事务一旦提交对数据库的改变就是永久的。
(二)事务管理举例
我们以银行赚钱为实例,创建如下数据表并且插入数据如下
CREATE TABLE `money` (
`id` bigint NOT NULL,
`person_name` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
`sum` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
依然配置用到了c3p0等数据库连接池,可以参少上面 标记 处,下面给出 dao、pojo、service 层的逻辑。
package com.qlu.service;
public interface MoneyService {
/**
* 转钱
* @param fromId 从哪儿
* @param toId 转给谁
* @param money 转账金额
* @return
*/
boolean changeMoney(int fromId,int toId,int money);
}
下面是数据持久层,在数据持久层的 changeMoney 有两个参数,你可以认为是改变某个 id 的 sum 值(sum表示总的金额)。
package com.qlu.dao;
public interface MoneyDao {
/**
* 根据id修改金额
* @param id
* @param money
* @return
*/
int changeMoney(int id,int money);
}
@Repository("moneyDao")
public class MoneyDaoImpl implements MoneyDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public int changeMoney(int id, int money) {
String sql = "update money set sum = sum + ? where id = ?";
Object[] args = {money,id};
return jdbcTemplate.update(sql,args);
}
}
下面是业务逻辑层,service 中的 changeMoney 表示从哪个 id 转移到哪个 id ,转多少钱。
package com.qlu.service;
public interface MoneyService {
/**
* 转钱
* @param fromId 从哪儿
* @param toId 转给谁
* @param money 转账金额
* @return
*/
boolean changeMoney(int fromId,int toId,int money);
}
package com.qlu.service.impl;
@Service("moneyService")
public class MoneyServiceImpl implements MoneyService {
@Autowired
private MoneyDao moneyDao;
@Override
public boolean changeMoney(int fromId, int toId, int money) {
/**
* from 减少
* to 增加
*/
//张三减少
int result1 = moneyDao.changeMoney(fromId,-1*money);
//李四增加
int result2 = moneyDao.changeMoney(toId,money);
return (result1>0 && result2>0 ? true:false);
}
}
通过 Ctrl + Shift + T 生成测试业务逻辑的测试类
@Test
public void textTx1() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-beans.xml");
MoneyService moneyService = applicationContext.getBean(MoneyService.class);
boolean result = moneyService.changeMoney(1, 2, 100);
System.out.println(result);
}
成功后我们可以看到数据库的变化
显然在这种正常的情况下没有错的,我们可以自定义一个 /by zero 异常在张三的钱减少和李四的钱增加之间来触发这个异常,
此时张三的钱没有了,但是李四的钱却没有增加,这一点类似于我们在多线程访问共享资源的情况一样,不给共享资源上锁,就可能出现两个线程信息不同步的情况。
为解决这个情况,Spring 提供了 TransactionManger,该功能依赖于 AOP,下面的配置都是老几样了,无非就是相当于把这个事务管理器当成一个增强函数来用,然后定义切面 aop-config,里面定义切点和增强组成切面,需要注意的是这个 aop:aspect 得换成我们事务管理的 aop:advisor
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd"
<!-- 配置 Spring 的事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务的消息通知 -->
<tx:advice id="tAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- 配置消息通知类型列表,监控的方法由上而下开始匹配,以及事务的传播特性 -->
<tx:method name="find*" propagation="SUPPORTS"/>
<tx:method name="query*" propagation="SUPPORTS"/>
<tx:method name="add*"/>
<tx:method name="delete*"/>
<tx:method name="update*"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.qlu.service.impl.*.*(..))"/>
<!--配置切面,将消息通知切入到Service层-->
<aop:advisor advice-ref="tAdvice" pointcut-ref="pointcut"></aop:advisor>
</aop:config>
关于配置事务的消息通知点这里。
再来跑一下,那自然还是有异常抛出,但是重点关注于数据库的数据,和上面可以说是完全一样,事务具有原子性(不可分割),这个减少 money 和增加 money 要不都做,要不都别做。
难办?那就都别办了,在张三减少了之后触发异常,程序被迫中止,那已经完成的 “张三减少” 也应该回滚成为没有减少的状态。
针对可能抛出异常的代码块,我们可能会选择使用 try...catch 环绕捕获异常
@Override
public boolean changeMoney(int fromId, int toId, int money) {
/**
* from 减少
* to 增加
*/
try {
//张三减少
int result1 = moneyDao.changeMoney(fromId,-1*money);
int exception = 1/0;
//李四增加
int result2 = moneyDao.changeMoney(toId,money);
return (result1>0 && result2>0 ? true:false);
} catch (Exception e) {
e.printStackTrace();
//throw e;
return false;
}
}
此时在这个业务内部就完成了对异常的处理(捕获到打印出信息),那在外部事务管理就不会捕获到这个异常。
解决方法:再把这个异常扔出来就行了,这样外部事务管理就可以捕获到异常信息
数据库没有变化(忘了应该在测试的 before 里面加个当前系统时间的)
(三)Spring 事务的传播
这个配置事务的消息通知就把它当成增强函数看就行,毕竟事务管理就是基于AOP的,你AOP能用注解事务管理也肯定是能用的。
在 tx:attributes 内的 tx:method 有如下属性:
1、name 属性:表示在执行哪些方法时,会触发消息通知,name 的属性值支持模糊匹配,例如:find*,表示 调用以 find 开头的方法时都会触发消息通知。
2、propagation 属性:表示事务的传播特性:
属性值“SUPPORTS”表示支持当前事务,如果当前没有事务, 就使用无事务机制;
属性值“REQUIRED”支持当前事务,有事务就加入到该事务中,如果没有事务则新建事务,默认值;
属性值 REQUIRES_NEW 如果有当前事务,则挂起当前事务,新建新事务,反之,直接新建事务;
属性值 MANDATORY 支持当前事务,如果没有事务,则抛出异常。
3、timeout 属性:事务超时时间 默认值是-1,-1 表示不超时,以秒为单位。
4、read-only 属性:事务是否只读,默认值是 false
关于事务的传播机制:
传播机制 | 说明 |
---|---|
REQUIRED | 如果当前没有事务,就创建一个事务,如果已经存在事务,就加入到这个事务。当前传播机制也是spring默认传播机制 |
REQUIRES_NEW | 新建事务,如果当前存在事务,就抛出异常。 |
SUPPORTS | 支持当前事务,如果当前没有事务, 就使用无事务机制。 |
NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
MANDATORY | 支持当前事务,如果没有事务,则抛出异常。 |
NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
NESTED | 如果当前存在事务,则在嵌套的事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作 |