今日内容
1.Spring中的JdbcTemplate
2.作业
Spring基于AOP的事务控制
3.Spring中的事务控制
基于XML的
基于注解的
1.Spring中的JdbcTemplate
1.1 JdbcTemplate 概述
它是spring框架中提供的一个对象,是对原始Jdbc API对象的简单封装(对JDBC)进行了
薄薄的封装。Spring矿建为我们提供了很多的操作模板类。
操作关系型数据的:
JdbcTemplate
HibernateTemplate
操作nosql数据库的:
RedisTemplate
操作消息队列的:
JmsTemplate
我们今天学习的主角JdbcTemplate在 spring-jdbc-5.0.2.RELEASE.jar 中,我们在导包的
时候,除了要导入这个jar包外,还需要导入一个spring-tx-5.0.2.RELEASE.jar(它是和事务
相关的)。
1.2 JdbcTemplate 的作用
它就是用于和数据库交互的,实现对表的CRUD操作
* jdbcTemplate的最基本用法1.
```
public static void main(String[] args) {
//准备数据源:spring的内置数据源
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/txl_spring");
ds.setUsername("root");
ds.setPassword("root");
//1.创建JdbcTemplate对象
//JdbcTemplate jt = new JdbcTemplate(ds);
JdbcTemplate jt = new JdbcTemplate();
//2.给JdbcTemplate设置数据源
jt.setDataSource(ds);
//2.执行操作
jt.execute("insert into account(name,money)values('ccc',1000)");
}
```
* jdbcTemplate的最基本用法2 --- IOC实现
```
<?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">
<!--配置jdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/txl_spring"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
</beans>
public static void main(String[] args) {
//1.获取容器:
ApplicationContext container = new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
JdbcTemplate jdbcTemplate = container.getBean("jdbcTemplate", JdbcTemplate.class);
//3. 执行操作
jdbcTemplate.execute("insert into account(name,money)values('ccc',2222)");
}
```
1.3 JdbcTemplate的CRUD操作
1 查询query
1 找出我们要用的查询方法:
2 RowMapper<T> rowMapper:
是一个接口且是T类型,所以不能直接用。
```
List<Account> accounts = jdbcTemplate.query("select * from account where money > ?",
new AccountRowMapper(), 1000);
for (Account account : accounts) {
System.out.println(account);
}
```
3 要传一个RowMapper<T>的实现类:AccountRowMapper,在里面定义我们要的封装策略。
```
class AccountRowMapper implements RowMapper<Account>{
/**
* 把结果集中的数据封装到Account中,然后由spring把每个Account加到集合中
* @param rs
* @param rowNum
* @return
* @throws SQLException
*/
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
Account account = new Account();
account.setId(rs.getInt("id"));
account.setName(rs.getString("name"));
account.setMoney(rs.getFloat("money"));
return account;
}
}
```
4 分析一下:第二个参数RowMapper<T> rowMapper
这是一个接口,定一个我们的封装策略。
我们要传一个它的实现类:AccountRowMapper,在里面定义我们要的封装策略。
```
class AccountRowMapper implements RowMapper<Account> {
//1.重写mapRow方法
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
...
}
}
```
所以我们将定义了封装策略的RowMapper<T>的子类AccountRowMapper传递过去。
2 查询query的简便方法
List<Account> accounts = jdbcTemplate.query("select * from account where money > ?",
new BeanPropertyRowMapper<Account>(Account.class), 1000f);
* 分析下第二个参数:new BeanPropertyRowMapper<Account>(Account.class)
Account:是我们要封装的对象
* 注意:
使用BeanProperytRowMapper要求sql数据查询出来的列和实体属性需要一一对应。
如果数据中列明和属性名不一致,在sql语句中需要用as重新取一个别名
3 查询一个
1.用查询所有的方法:返回List<T>
```
List<Account> accounts1 = jdbcTemplate.query("select * from account where id = ?",
new BeanPropertyRowMapper<Account>(Account.class), 1);
System.out.println(accounts1.isEmpty()?"没有内容":accounts1.get(0));
```
2. 用专门的查询一个方法
```
Account account = jdbcTemplate.queryForObject("select * from account where id = ?",
new BeanPropertyRowMapper<Account>(Account.class), 4);
```
3.但是实际上由于一个查询所有的方法就可以解决两种情况,所以一般用查询所有方法解决。
4. 查询一行一列
```
Long count = jdbcTemplate.queryForObject("select count(*) from account where money > ?",
Long.class, 1000);
System.out.println(count);
```
查询出来的结果会给你转成Long类型,用Long是因为实际开发中数据量比较大。
1.4 在dao中定义JdbcTemplate属性:第一种方式
1.4.1 准备实体类
```
public class Account implements Serializable {
private Integer id;
private String name;
private Float money;
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Float getMoney() {
return money;
}
public void setMoney(Float money) {
this.money = money;
}
}
```
1.4.2 在dao中定义JdbcTemplate
```
/**
* 账户的持久层接口
*/
public interface IAccountDao {
/**
* 根据id查询张哈
* @param accountId
* @return
*/
Account findAccountById(Integer accountId);
/**
* 根据名称查询账户
* @param accountName
* @return
*/
Account findAccountByName(String accountName);
/**
* 更新账户
* @param account
*/
void updateAccount(Account account);
}
/**
* 账户的持久层实现类
持久层实现类中有定义JdbcTemplate的属性和set方法
*/
public class AccountDaoImpl implements IAccountDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public Account findAccountById(Integer accountId) {
List<Account> accountList =
jdbcTemplate.query("select * from account where id = ?",
new BeanPropertyRowMapper<Account>(Account.class), accountId);
return accountList.isEmpty()?null:accountList.get(0);
}
public Account findAccountByName(String accountName) {
List<Account> accounts = jdbcTemplate.query("select * from account where name = ?",
new BeanPropertyRowMapper<Account>(Account.class), "ccc");
if(accounts.size() > 1){
throw new RuntimeException("结果集不唯一");
}
if(accounts.isEmpty()){
return null;
}
return accounts.get(0);
}
```
1.4.3
配置文件: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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置账户的持久层-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<!-- 注入jdbcTemplate -->
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<!--配置jdbcTemplate:数据库操作模版-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/txl_spring"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
</beans>
```
1.4.4 分析思考
思考:此种方式有什么问题?
分析:
有个小问题,就是当我们有多个dao时,每个dao都有一些重复性的代码,注入和定义的重复代码。
```
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
```
* 那么我们怎么把它抽取出来呢?
1.5 在dao中使用JdbcTemplate:第二种方式
让dao继承JdbcDaoSupport:此类用于抽取dao中的重复代码
JdbcDaoSupport是spring框架为我们提供的一个类,该类中定义了一个JdbcTemplate对象属性
。要想获得该属性可以通过两种方式:
1.注入:DataSource
2.注入:JdbcTemplate
1.我们分析一下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();
}
}
protected JdbcTemplate createJdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
public final void setJdbcTemplate(@Nullable JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
initTemplateConfig();
}
public final JdbcTemplate getJdbcTemplate() {
return this.jdbcTemplate;
}
```
源码里面有一个JdbcTemplate属性:private JdbcTemplate jdbcTemplate。;
我们可以通过set方法为其注入,再通过get获取。
里面还有一个setDataSource方法,可以为其注入dataSource。setDataSource
方法体内判断是否注入了dataSource,如果注入就创建jdbcTemplate。
2. 分析一下dao继承JdbcDaoSupport。
我们让dao继承JdbcDaoSupport,然后在dao中为也有了JdbcTemplate属性等方法。
在dao中就不用在定义JdbcTemplate属性和set方法。
在bean.xml位dao注入dataSource后,由于继承了JdbcDaoSupport中的setdataSource()
方法和createJdbcTemplate(dataSource)。就会生成dataSource和JdbcTemplate放到了
IOC容器中。
那么我们也无需在bean.xml再为dao注入JdbcTemplate了。
也不用再配置JdbcTemplate的bean了。
3. 代码:
4.思考和分析
思考:两种方式的在dao中使用JdbcTemplate有什么区别。
分析:
1.在dao中定义JdbcTemplate属性:第一种方式。代码会较臃肿,但是
适用于所有的配置方式。
2.让Dao继承JdbcDaoSupport的方式:第一种方式。代码精简了。
但是只能用XML的方式配置,由于要导入的:
import org.springframework.jdbc.core.support.JdbcDaoSupport;
所以不能用注解配置JdbcTemplate属性。
2 作业 Spring基于AOP的事务控制
2.1 XML配置实现
1.实体类: Account
```
@Data
public class Account implements Serializable {
private Integer id;
private String name;
private Float money;
}
```
2.账户的业务层实现类:AccountServiceImpl
```
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao){
this.accountDao = accountDao;
}
public void transfer(String sourceName, String targetName, float money) {
System.out.println("transfer...");
//1.根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2.根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//3.转出账户减钱
source.setMoney(source.getMoney() - money);
//4.转入账户加钱
target.setMoney(target.getMoney() + money);
//5.更新转出账户
accountDao.updateAccount(source);
//int i = 1/0; //模拟转账异常
//6.更新转入账户
accountDao.updateAccount(target);
}
}
```
3.账户的持久层实现类
```
public class AccountDaoImpl implements IAccountDao {
private QueryRunner runner;
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
public void setRunner(QueryRunner runner){
this.runner = runner;
}
public void updateAccount(Account account) {
try {
runner.update(connectionUtils.getThreadConnection(),"update account set name = ?, money = ? where id = ?", account.getName(),account.getMoney(), account.getId());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Account findAccountByName(String accountName) {
try {
List<Account> accounts = runner.query(connectionUtils.getThreadConnection(),"select * from account where
name = ?", new BeanListHandler<Account>(Account.class), accountName);
if(accounts == null || accounts.size() == 0){
return null;
}
if(accounts.size() > 1){
throw new RuntimeException("结果集不唯一:数据有问题。");
}
return accounts.get(0);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
```
4.连接工具类
从数据源中获取一个连接,并且实现和线程的绑定
```
public class ConnectionUtils {
//1.ThreadLocal对象,用泛型指定管理对象是java.sql.Connection
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
@Autowired
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 获取当前线程上的连接
* 控制事务,是靠connection把手动提交改成自动提交
* 再通过commit和rollback来控制事务。
* @return
*/
public Connection getThreadConnection(){
try{
//1.先从ThreadLocal上获取
Connection coon = tl.get();
/**
* 2.判断当前线程上是否有连接:
*/
if(coon == null){
//3.从数据源中获取一个连接,并且存入ThreadLocal中
coon = dataSource.getConnection();
//4.存入ThreadLocal
tl.set(coon);
}
//5.返回当前线程上的连接
return coon;
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 把连接和线程解绑
*/
public void removeConnection(){
tl.remove();
}
}
```
5.事务管理类
```
public class TransactionManager {
@Autowired
private ConnectionUtils connectionUtils;
//等待Spring将其注入
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
//开启事务
public void beginTransaction(){
try{
connectionUtils.getThreadConnection().setAutoCommit(false);
} catch (Exception e){
e.printStackTrace();
}
}
// 提交事务
public void commit(){
try{
connectionUtils.getThreadConnection().commit();
} catch (Exception e){
e.printStackTrace();
}
}
//回滚事务
public void rollback(){
try{
connectionUtils.getThreadConnection().rollback();
} catch (Exception e){
e.printStackTrace();
}
}
// 释放连接
public void release(){
try{
connectionUtils.getThreadConnection().close();
//解绑
connectionUtils.removeConnection();
} catch (Exception e){
e.printStackTrace();
}
}
}
```
6.配置AOP切面。(添加事务/为切入点方法添加通知)
```
<?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: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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置service对象-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置Dao对象-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<property name="runner" ref="queryRunner"></property>
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!--配置QueryRunner对象-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
</bean>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/txl_spring"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--配置Connection的工具类:ConnectionUtils-->
<bean id="connectionUtils" class="com.itheima.utils.ConnectionUtils">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务管理类:TransactionManager
也就是提供了重复代码的通知类,将其交给spring来管理。
-->
<bean id="transactionManager" class="com.itheima.utils.TransactionManager">
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!--配置AOP-->
<aop:config>
<!--配置一个通用的切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"/>
<aop:aspect id="txAdvice" ref="transactionManager">
<!--配置前置通知:开启事务-->
<aop:before method="beginTransaction" pointcut-ref="pt1"></aop:before>
<!--配置后置通知:提交事务-->
<aop:after-returning method="commit" pointcut-ref="pt1"></aop:after-returning>
<!--配置异常通知:回滚事务-->
<aop:after-throwing method="rollback" pointcut-ref="pt1"></aop:after-throwing>
<!--配置最终通知:释放连接-->
<aop:after method="release" pointcut-ref="pt1"></aop:after>
</aop:aspect>
</aop:config>
```
2.2 注解配置实现AOP
2.2.1 前后异终通知---AOP
1.先改造一下IOC:导入一个context的名称空间和对应约束
2. 配置spring创建容器时要扫描的包:告知在哪写了注解。
<context:component-scan base-package="com.itheima"></context:component-scan>
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:aop="http://www.springframework.org/schema/aop"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--配置spring创建容器时要扫描的包-->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!--配置QueryRunner对象-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
</bean>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/txl_spring"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--开启spring对注解AOP的支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
```
3.事务管理类
```
@Component("transactionManager")
@Aspect
public class TransactionManager {
@Resource(name="connectionUtils")
private ConnectionUtils connectionUtils;
@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
private void pt1(){
}
/**
* 开启事务
*/
@Before("pt1()")
public void beginTransaction(){
try{
connectionUtils.getThreadConnection().setAutoCommit(false);
} catch (Exception e){
e.printStackTrace();
}
}
/**
* 提交事务
*/
@AfterReturning("pt1()")
public void commit(){
try{
connectionUtils.getThreadConnection().commit();
} catch (Exception e){
e.printStackTrace();
}
}
/**
* 回滚事务
*/
@AfterThrowing("pt1()")
public void rollback(){
try{
connectionUtils.getThreadConnection().rollback();
} catch (Exception e){
e.printStackTrace();
}
}
/**
* 释放连接
*/
@After("pt1()")
public void release(){
try{
connectionUtils.getThreadConnection().close();
//解绑
connectionUtils.removeConnection();
} catch (Exception e){
e.printStackTrace();
}
}
}
```
4.连接工具类
```
@Component("connectionUtils")
public class ConnectionUtils {
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
@Autowired
private DataSource dataSource;
public Connection getThreadConnection(){
try{
//1.先从ThreadLocal上获取
Connection coon = tl.get();
if(coon == null){
//3.从数据源中获取一个连接,并且存入ThreadLocal中
coon = dataSource.getConnection();
coon.setAutoCommit(false);
//4.存入ThreadLocal
tl.set(coon);
}
//5.返回当前线程上的连接
return coon;
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void removeConnection(){
tl.remove();
}
}
```
5.业务层
```
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
public List<Account> findAllAccount() {
return accountDao.findAllAccount();
}
public Account findAccountById(Integer accountId) {
return accountDao.findAccountById(accountId);
}
public void saveAccount(Account account) {
accountDao.saveAccount(account);
}
public void updateAccount(Account account) {
accountDao.updateAccount(account);
}
public void deleteAccount(Integer accountId) {
accountDao.deleteAccount(accountId);
}
public void transfer(String sourceName, String targetName, float money) {
System.out.println("transfer...");
//1.根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2.根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//3.转出账户减钱
source.setMoney(source.getMoney() - money);
//4.转入账户加钱
target.setMoney(target.getMoney() + money);
//5.更新转出账户
accountDao.updateAccount(source);
//int i = 1/0; //模拟转账异常
//6.更新转入账户
accountDao.updateAccount(target);
}
}
```
6.持久层...
7. 分析:
先回想一下,注解实现AOP的一个小问题:
先调用最终通知,再调用异常通知/后置通知。
所以先调用最终通知就会将coon关闭了,再将coon与当前线程解绑。
```
@After("pt1()")
public void release(){
try{
connectionUtils.getThreadConnection().close();
//解绑
connectionUtils.removeConnection();
} catch (Exception e){
e.printStackTrace();
}
}
```
那么再调用后置通知时:会先调用connectionUtils.getThreadConnection().commit();
发现没有连接了,它会再从数据源中拿一个新的coon然后绑上去。
为什么可以拿新的coon,因为我们在ConnectionUtils类中写的方法:
connectionUtils.getThreadConnection()
前置通知执行完了(因为前置通知会将autocommit置false),才从数据源中拿一个新的
coon绑上去。注意从数据池中得到的新的coon的autocommit=true,所以再提交commit就会报错:
Can't call commit when autocommit=true
那我们在ConnectionUtils.getThreadConnection()获取新的coon时,将其AutoCommit设为false。
设置成false之后,那么可以提交了,但是数据库中的数据会改吗?
因为刚才操作的那个coon被先关闭了,而现在这个新的coon虽然你提交,没有异常产生了。但是
新的coon里面没有任何的执行操作,什么都提交不了。
8. 所以这就是Spring基于注解的配置AOP时:通知的执行调用是有问题的,
如果非要用注解:我们用环绕通知。
2.2.2 环绕通知 -- 注解实现AOP
在事务管理类中,不配置前置通知/后置通知/异常通知/最终通知等注解。
* 配置一个环绕通知:
3.Spring提供的工具来进行事务控制---准备工作
3.1 事务控制我们要明确的
1. JavaEE体系进行分层开发,事务处理位于业务层,Spring提供了分层设计业务层的事务处理解决方案。
2. spring框架为我们提供了一组事务控制的接口。具体在后面的第二小节介绍。这组接口是在
spring-tx-5.0.2.RELEASE.jar中。
所以要先导入依赖
3. spring的事务控制都是基于AOP的,它既可以使用编程的方式实现,也可以使用配置的方式实现。
4. 注意:我们学习的重点是使用配置的方式实现。
3.2 PlatformTransactionManager:事务管理类接口
此接口是spring的事务管理器,它里面提供了我们常用的操作事务的方法,如下图:
我们在开发中都是使用它的实现类,真正管理事务的对象:
* org.springframework.jdbc.datasource.DataSourceTransactionManager
使用Spring JDBC或iBatis 进行持久化数据时使用
* org.springframework.orm.hibernate5.HibernateTransactionManager
使用Hibernate版本进行持久化数据时使用
3.3 事务信息类接口: 它是事务的定义信息对象,里面有如下方法:
1. 获取事务对象名称: String getName()
2. 获取事务隔离级: int getIsolationLevel()
事务的隔离级别一共有四个,spring默认情况下使用的是数据库的默认隔离级别。
3.获取事务传播行为: int getPropagationBehavior()
什么情况下必须有事务,什么情况下可以没有事务。
我们暂时考虑:增删改必须有事务。
查询时可以没有事务。
4.指定事务的超时时间: int getTimeout()
提交回滚多少时间后就过期了:我们可以通过配置的方式永远斗不过期。
5.获取事务是否只读: boolean isReadOnly()
只读:执行查询时,也会开启事务
读写型事务:增删改,开启事务。
3.4 TransactionStatus接口:事务状态
此接口提供的是事务具体的运行状态,方法介绍如下图:
4. Spring自己的基于XML的事务控制
步骤:
1. 配置spring的事务管理器
这个里面有提交和回滚等方法。然后在第2步,指明其是通知。
2. 配置事务的通知
此时我们要导入事务的约束: tx的名称空间和约束,同时也需要aop的约束 xmlns:tx
使用tx:advice配置事务通知
属性:
id:给事务通知起一个唯一标志
transaction-manager:给事务通知提供一个事务管理器引用
3. 配置AOP中的通用切入点表达式
4. 建立事务通知和切入点表达式的对应关系
5. 配置事务的属性
是在事务的通知tx:advice标签的内部
明确:因为用的是spring自带的事务管理器,里面有提交和回滚等方法。所以我们只要指定管理器
和切入点方法的联系即可。不用我们在设置里面具体的执行步骤。
```
<!--1.配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--2.配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--4.为service中的所有方法,配置事务的属性
isolation="" : 指定事务的隔离级别,默认值DEFAULT表示使用数据库的默认隔离级别
no-rollback-for="" : 用于指定一个异常,产生该异常时不回滚,其他异常会滚。没有默认值,表示任何异常都会滚。
propagation="" : 指定事务的传播级别:
默认REQUIRED:没有就新建一个,有就添加进去。适合增删改。
SUPPORTS:没事务就用,没有就不用。适合查询
read-only="" : 事务是否只读:查询方法可以设为只读true,增删改设为读写。
rollback-for="" : 用于指定一个异常,产生该异常时回滚,其他异常不会滚。没有默认值,表示任何异常都会滚。
timeout="" : 指定事务的超时时间:,默认-1,永不超时。指定数值以秒为单位。
-->
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<!--为service中的findxxx()方法,配置事务的属性
这个优先级是高于上面的全通配的
-->
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<!--3.配置AOP-->
<aop:config>
<!--3.1配置通用点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"/>
<!--3.2建立切入点表达式和事务通知的对应关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
```
5. Spring基于注解和XML结合的事务控制
引入相应的名称空间和约束
1.配置事务管理器
```
<!--1.配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
```
2.开启spring对注解事务的支持
```
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
```
3.在需要事务支持的地方使用@Transactional注解
不仅仅在类上,可以指定到具体的方法
4.完整的bean.xml:注解和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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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/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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--配置spring创建容器时要扫描的包-->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!--配置jdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/txl_spring"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--spring基于注解的声明式事务控制 配置步骤
1.配置事务管理器
2.开启spring对注解事务的支持
3.在需要事务支持的地方使用@Transactional注解
-->
<!--1.配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--2.开启spring对注解事务的支持-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
```
6. Spring基于注解的事务控制 (没有bean.xml)
1.账户的持久层实现类
```
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
//1.按方法名查询一个
public Account findAccountByName(String accountName) {
}
//2.更新账户
public void updateAccount(Account account) {
}
```
2.账户的业务层实现类:让事务控制和业务层的方法分离
在需要事务支持的地方使用@Transactional注解
```
@Service("accountService")
@Transactional //事务的属性全在里面:read-only/timeout/isolation...
public class AccountServiceImpl implements IAccountService {
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void transfer(String sourceName, String targetName, Float money) {
Account source = accountDao.findAccountByName(sourceName);
Account target = accountDao.findAccountByName(targetName);
source.setMoney(source.getMoney() - money);
target.setMoney(target.getMoney() + money);
accountDao.updateAccount(source);
//int i = 1/0; //模拟转账异常
accountDao.updateAccount(target);
}
}
```
3. 连接资源配置文件:jdbcConfig.properties
```
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/txl_spring
jdbc.username=root
jdbc.password=root
```
4. 3个配置类
主配置类:public class SpringConfiguration,引入子配置类
子配置类1:public class JdbcConfig,和连接数据库相关的配置类
子配置类2:public class TransactionConfig,和事务相关的配置类
4.1: 子配置类1:public class JdbcConfig,和连接数据库相关的配置类
```
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
//创建JdbcTemplate对象
@Bean(name="jdbcTemplate")
public JdbcTemplate createJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
//创建数据源对象
@Bean(name="dataSource")
public DataSource createDataSource(){
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setPassword(password);
ds.setUsername(username);
return ds;
}
}
```
4.2: 子配置类2:public class JdbcConfig,public class TransactionConfig,和事务相关的配置类
```
public class TransactionConfig {
/**
* 创建管理器对象
* PlatformTransactionManager 接口
* new DataSourceTransactionManager(dataSource); 返回它的子类
* @param dataSource
* @return
*/
@Bean(name="transactionManager")
public PlatformTransactionManager createTransactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
```
4.3 : 主配置类:public class SpringConfiguration
```
/**
* spring的配置类,相当于bean.xml
*/
@Configuration //这个注解可写可不写:因为可以把这个类当参数传递给测试方法
@ComponentScan("com.itheima")
@Import({JdbcConfig.class,TransactionConfig.class})
@PropertySource("classpath:jdbcConfig.properties") //指明连接资源
@EnableTransactionManagement //开启注解支持
public class SpringConfiguration {
}
```
5. 测试方法---spring整合的junit
```
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountServiceTest {
@Autowired
private IAccountService accountService;
@Test
public void testTransfer(){
accountService.transfer("bbb", "ccc", 100f);
}
}
```
7. 编程式的事务控制:代码实现的事务控制(用的很少)
上面是Spring声明类型的的事务控制。
1.事务的控制都离不开 提交和回滚这两个操作。在spring中将提交和回滚放到了事务管理器中,
所以无论如何我们先配置一个事务管理器,且需要注入数据源
```
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
```
2.事务模版对象
```
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"></bean>
```
我们在要使用事务的地方添加一个事务模版对象属性。
```
public class AccountServiceImpl implements IAccountService {
private TransactionTemplate transactionTemplate;
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
}
```
3.再仿照TransactionTemplate中的public <T> T execute(TransactionCallback<T> action)来进行编程式
事务控制。
serviec层中用到的方法全都要编写代码
* public class AccountServiceImpl
```
public class AccountServiceImpl implements IAccountService {
private TransactionTemplate transactionTemplate;
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
public Account findAccountById(final Integer accountId) {
return transactionTemplate.execute(new TransactionCallback<Account>() {
public Account doInTransaction(TransactionStatus status) {
return accountDao.findAccountById(accountId);
}
});
}
public void transfer(final String sourceName, final String targetName, final Float money) {
transactionTemplate.execute(new TransactionCallback<Object>() {
public Object doInTransaction(TransactionStatus status) {
System.out.println("transfer...");
//1.根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2.根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//3.转出账户减钱
source.setMoney(source.getMoney() - money);
//4.转入账户加钱
target.setMoney(target.getMoney() + money);
//5.更新转出账户
accountDao.updateAccount(source);
//int i = 1/0; //模拟转账异常
//6.更新转入账户
accountDao.updateAccount(target);
return null;
}
});
}
}
```
4. 在业务层注入事务模版对象
在事务模版对象注入事务管理器对象
```
<!--配置业务层-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
<property name="transactionTemplate" ref="transactionTemplate"></property>
</bean>
<!--2.事务模版对象-->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"></property>
</bean>
```
5. 问题:由于是编写代码实现的事务控制,会导致业务层的重复代码变得更多。
所以出场机会不高。
8. SPRING 5新特性。
8.1 JDK版本要求
spring5.0在2017年9月发布了它的GA(通用)版本。该版本是基于jdk8编写的,所以jdk8以下版本
将无法使用。同时,可以兼容jdk9版本。
tomcat版本要求8.5及以上。
我们使用jdk8构建工程,可以降版编译。但是不能使用jdk8以下版本构建工程。
由于jdk和tomcat版本的更新,我们的IDE也需要同时更新。
8.2 利用jdk8版本更新的内容
1. 基于JDK8的反射增强,请看下面的代码:
```
public class Test {
//循环次数定义:10亿次
private static final int loopCnt = 1000 * 1000 * 1000;
public static void main(String[] args) throws Exception {
//输出JDK的版本
System.out.println("java.version=" + System.getProperty("java.version"));
t1();
t2();
t3();
}
//每次重新生成新对象
public static void t1(){
long s = System.currentTimeMillis();
for (int i = 0; i < loopCnt; i++) {
Person p = new Person();
p.setAge(31);
}
long e = System.currentTimeMillis();
System.out.println("循环10亿次创建对象的时间:" + (e-s));
}
//每次给同一个对象赋值
public static void t2(){
long s = System.currentTimeMillis();
Person p = new Person();
for (int i = 0; i < loopCnt; i++) {
p.setAge(32);
}
long e = System.currentTimeMillis();
System.out.println("循环10亿次给同一对象赋值的时间:" + (e - s));
}
//使用反射创建对象
public static void t3() throws Exception{
long s = System.currentTimeMillis();
Class<Person> c = Person.class;
Person p = c.newInstance();
Method m =c.getMethod("setAge", int.class);
for (int i = 0; i < loopCnt; i++) {
m.invoke(p, 33);
}
long e = System.currentTimeMillis();
System.out.println("循环10亿次反射创建对象的时间:" + (e - s));
}
static class Person{
private int age = 20;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
}
```
2. @NonNull注解和@Nullable注解的使用
用 @Nullable 和 @NotNull注解来显示表明可为空的参数和以及返回值。这样就够在编译的时候
处理空值而不是在运行时抛出 NullPointerExceptions。
3. 第三:日志记录方面
Spring Framework 5.0 带来了 Commons Logging 桥接模块的封装, 它被叫做 spring-jcl 而不
是标准的 Commons Logging。当然,无需任何额外的桥接,新版本也会对 Log4j 2.x, SLF4J, JUL ( java.util.logging) 进行自动检测。
4. 核心容器的更新