1、spring中的JdbcTemplate
JdbcTemplate的作用:(web里学过 执行sql语句。。。
它就是用于和数据库交互的,实现对表的CRUD操作
我们今天的主角在 spring-jdbc-5.0.2.RELEASE.jar 中,我们在导包的时候,除了要导入这个 jar 包 外,还需要导入一个 spring-tx-5.0.2.RELEASE.jar(它是和事务相关的)。 妈呀跳了吧直接导mybatis的包就行了!!!!
如何创建该对象:之前学的结合c3p0或德鲁伊连接池 封装的JDBCUtils直接用(详见web笔记)
这里应该是配置吧详见pdf先跳了。。。因为mybatis用注解就可以执行sql。。。
对象中的常用方法:
2、讲解作业:spring基于AOP的事务控制
ps:之前day3那段还用到了线程,我感觉有空得回去学习一下,我的线程学的不太好。(因为课题传感器应该是用线程调用不应该在主线里面搞。。。
新建模块day04_eesy_02account_aoptx_xml 导入spring-context,spring-test,dbutils,mysql-connector-java,c3p0,junit坐标
这个全xml式的我不太喜欢,还是喜欢注解注入更加清楚。。。至少dao和service得注解了,还有他俩的属性new用@auto注解了。。(下面这个day04-03就是!!
但这个测试类又写了基于注解的spring整合junit给service调用时依赖注入了
复制粘贴它,新建基于注解的模块day04_eesy_03account_aoptx_anno(还是用常用注解和少量xml,不用写config包,就day2第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" 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="runner" 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/eesy2"></property> <property name="user" value="root"></property> <property name="password" value="root"></property> </bean> <!--开启spring对注解aop的支持--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
Dao层(要是依赖了mabatis包就不用写dao层的接口了)
/** * 账户的持久层接口 */ public interface IAccountDao { /** * 查询所有 * @return */ List<Account> findAllAccount(); /** * 查询一个 * @return */ Account findAccountById(Integer accountId); /** * 保存 * @param account */ void saveAccount(Account account); /** * 更新 * @param account */ void updateAccount(Account account); /** * 删除 * @param acccountId */ void deleteAccount(Integer acccountId); /** * 根据名称查询账户 * @param accountName * @return 如果有唯一的一个结果就返回,如果没有结果就返回null * 如果结果集超过一个就抛异常 */ Account findAccountByName(String accountName); }IAccountDao接口
/** * 账户的持久层实现类 */ @Repository("accountDao") public class AccountDaoImpl implements IAccountDao { @Autowired private QueryRunner runner; @Autowired private ConnectionUtils connectionUtils; @Override public List<Account> findAllAccount() { try{ return runner.query(connectionUtils.getThreadConnection(),"select * from account",new BeanListHandler<Account>(Account.class)); }catch (Exception e) { throw new RuntimeException(e); } } @Override public Account findAccountById(Integer accountId) { try{ return runner.query(connectionUtils.getThreadConnection(),"select * from account where id = ? ",new BeanHandler<Account>(Account.class),accountId); }catch (Exception e) { throw new RuntimeException(e); } } @Override public void saveAccount(Account account) { try{ runner.update(connectionUtils.getThreadConnection(),"insert into account(name,money)values(?,?)",account.getName(),account.getMoney()); }catch (Exception e) { throw new RuntimeException(e); } } @Override 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); } } @Override public void deleteAccount(Integer accountId) { try{ runner.update(connectionUtils.getThreadConnection(),"delete from account where id=?",accountId); }catch (Exception e) { throw new RuntimeException(e); } } @Override 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); } } }AccountDaoImpl.java
Service层
/** * 账户的业务层接口 */ public interface IAccountService { /** * 查询所有 * @return */ List<Account> findAllAccount(); /** * 查询一个 * @return */ Account findAccountById(Integer accountId); /** * 保存 * @param account */ void saveAccount(Account account); /** * 更新 * @param account */ void updateAccount(Account account); /** * 删除 * @param acccountId */ void deleteAccount(Integer acccountId); /** * 转账 * @param sourceName 转出账户名称 * @param targetName 转入账户名称 * @param money 转账金额 */ void transfer(String sourceName, String targetName, Float money); //void test();//它只是连接点,但不是切入点,因为没有被增强 }IAccountService接口
/** * 账户的业务层实现类 * * 事务控制应该都是在业务层 */ @Service("accountService") public class AccountServiceImpl implements IAccountService{ @Autowired private IAccountDao accountDao; @Override public List<Account> findAllAccount() { return accountDao.findAllAccount(); } @Override public Account findAccountById(Integer accountId) { return accountDao.findAccountById(accountId); } @Override public void saveAccount(Account account) { accountDao.saveAccount(account); } @Override public void updateAccount(Account account) { accountDao.updateAccount(account); } @Override public void deleteAccount(Integer acccountId) { accountDao.deleteAccount(acccountId); } @Override public void transfer(String sourceName, String targetName, Float money) { System.out.println("transfer...."); //2.1根据名称查询转出账户 Account source = accountDao.findAccountByName(sourceName); //2.2根据名称查询转入账户 Account target = accountDao.findAccountByName(targetName); //2.3转出账户减钱 source.setMoney(source.getMoney()-money); //2.4转入账户加钱 target.setMoney(target.getMoney()+money); //2.5更新转出账户 accountDao.updateAccount(source); // int i=1/0;//搞个异常在这里 //2.6更新转入账户 accountDao.updateAccount(target); } }AccountServiceImpl.java
实体类
/** * 账户的实体类 */ public class Account implements Serializable { private Integer id; private String name; private Float 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; } @Override public String toString() { return "Account{" + "id=" + id + ", name='" + name + '\'' + ", money=" + money + '}'; } }Account
Utils包写连接工具和通知们
/** * 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定 */ @Component("connectionUtils") public class ConnectionUtils { private ThreadLocal<Connection> tl = new ThreadLocal<Connection>(); @Autowired private DataSource dataSource; /** * 获取当前线程上的连接 * @return */ public Connection getThreadConnection() { try{ //1.先从ThreadLocal上获取 Connection conn = tl.get(); //2.判断当前线程上是否有连接 if (conn == null) { //3.从数据源中获取一个连接,并且存入ThreadLocal中 conn = dataSource.getConnection(); conn.setAutoCommit(false); tl.set(conn); } //4.返回当前线程上的连接 return conn; }catch (Exception e){ throw new RuntimeException(e); } } /** * 把连接和线程解绑 */ public void removeConnection(){ tl.remove(); } }ConnectionUtils
这个通知们的顺序有问题虽然没报错但是数据库没变化。
(弹幕说把@after和@afterreturning换一下,实在机智,但是after必执行,发生异常就冲突了,所以不行得换成环绕通知。也可以把aspect用xml不用注解
老师的带大家debug查看 一直运行下一步和放过,我有点晕去读了一下博客放过原来是跳到下一个断点的意思
关于debug的粗略知识:https://www.cnblogs.com/Bowu/p/4026117.html)
换成环绕通知注解
/** * 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接 */ @Component("txManager") @Aspect public class TransactionManager { @Pointcut("execution(* com.itheima.service.impl.*.*(..))") private void pt1(){ } @Autowired private 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(); } } @Around("pt1()") public Object aroundAdvice(ProceedingJoinPoint pjp){ Object rtValue = null; try { //1.获取参数 Object[] args = pjp.getArgs(); //2.开启事务 this.beginTransaction();//前面那个 //3.执行方法 rtValue = pjp.proceed(args); //4.提交事务 this.commit();//前面那个 //返回结果 return rtValue; }catch (Throwable e){ //5.回滚事务 this.rollback();//前面那个 throw new RuntimeException(e); }finally { //6.释放资源 this.release();//前面那个 } } }
测试类(还是使用spring整合junit)
/** * 使用Junit单元测试:测试我们的配置 */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:bean.xml") public class AccountServiceTest { @Autowired private IAccountService as; @Test public void testTransfer(){ as.transfer("aaa","bbb",100f);//测试aaa转给bbb 100元 } }
运行结果:数据库中aaa少了100,bbb多了100
3、spring中的事务控制
spring提供了关于事务控制的jar包(spring-tx)。。。我们不用手动写那么多了
基于XML的
新建模块day04_eesy_04tx(这个只搭环境不做配置)dao层只导了spring的jdbc包,数据源写在bean配置
新建模块day04_eesy_05tx_xml
bean.xml配置事务!有用到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:tx="http://www.springframework.org/schema/tx" 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/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"> <!-- 配置账户的业务层--> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean> <!-- 配置账户的持久层--> <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"> <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/eesy2"></property> <property name="username" value="root"></property> <property name="password" value="root"></property> </bean> <!-- spring中基于XML的声明式事务控制配置步骤 1、配置事务管理器 2、配置事务的通知 此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的 使用tx:advice标签配置事务通知 属性: id:给事务通知起一个唯一标识 transaction-manager:给事务通知提供一个事务管理器引用 3、配置AOP中的通用切入点表达式 4、建立事务通知和切入点表达式的对应关系 5、配置事务的属性 是在事务的通知tx:advice标签的内部 --> <!-- 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"> <!-- 5.通知标签内部配置事务的属性(重要 这里是事务控制对应的业务层方法的属性!!!!! isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。 propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。 read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。 timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。 rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。 no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。 --> <tx:attributes> <tx:method name="*" propagation="REQUIRED" read-only="false"/> <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method> </tx:attributes> </tx:advice> <!-- 配置aop--> <aop:config> <!-- 3.配置切入点表达式(映射业务层所有方法--> <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut> <!--4.建立切入点表达式和事务通知的对应关系 (第一次见 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor> </aop:config> </beans>
还是原来的测试类:aaa转给bbb 100元 成功
基于注解的
<!--bean.xml中 开启 spring 对注解事务的支持 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource">
</property>
</bean>
然后就是在业务类中使用@Transactional 注解
详见pdf 后面的也略了,spring4天完结!!
主要就是学了IoC解耦,就是用依赖注入把对象类搞成bean,new对象时xml写property标签,注解用@Autowired等等。AOP就是把重复代码抽出来设为通知,用切面配置和切面标签(或注解)把通知织入到业务层对应的切入点方法。