16 spring整合mybatis
16.1 前情代码
- 实体类
public class Account {
private Integer id;
private String name;
private Double 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 Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
- AccountDao接口
public interface AccountDao {
@Insert("insert into tbl_account(name,money)values(#{name},#{money})")
void save(Account account);
@Delete("delete from tbl_account where id = #{id} ")
void delete(Integer id);
@Update("update tbl_account set name = #{name} , money = #{money} where id = #{id} ")
void update(Account account);
@Select("select * from tbl_account")
List<Account> findAll();
@Select("select * from tbl_account where id = #{id} ")
Account findById(Integer id);
}
- App实现
public class App {
public static void main(String[] args) throws IOException {
// 1. 创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 2. 加载SqlMapConfig.xml配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
// 3. 创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
// 4. 获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 5. 执行SqlSession对象执行查询,获取结果User
AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
Account ac = accountDao.findById(2);
System.out.println(ac);
// 6. 释放资源
sqlSession.close();
}
}
- AccountService接口
public interface AccountService {
void save(Account account);
void delete(Integer id);
void update(Account account);
List<Account> findAll();
Account findById(Integer id);
}
- AccountServiceImpl接口实现类
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
public void save(Account account) {
accountDao.save(account);
}
public void update(Account account){
accountDao.update(account);
}
public void delete(Integer id) {
accountDao.delete(id);
}
public Account findById(Integer id) {
return accountDao.findById(id);
}
public List<Account> findAll() {
return accountDao.findAll();
}
}
16.2 整合
1. 分析
sqlSession是工厂造出来的,已经存在,现在调用而已
sqkSessionFactory是核心对象
下面配置是围绕此对象进行的
- pom准备
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--spring操作与数据库有关的-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<!--spring整合一个mybits的jar包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
- spring配置类
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}
- 自动状态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;
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
- 注解简化mybatis配置
public class MybatisConfig {
//定义bean,SqlSessionFactoryBean,用于产生SqlSessionFactory对象
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
ssfb.setTypeAliasesPackage("com.itheima.domain");
ssfb.setDataSource(dataSource);
return ssfb;
}
//定义bean,返回MapperScannerConfigurer对象
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.itheima.dao");
return msc;
}
}
- 原来App类不要了,重新写App2
public class App2 {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
AccountService accountService = ctx.getBean(AccountService.class);
Account ac = accountService.findById(1);
System.out.println(ac);
}
}
17 Spring整合junit
- maven准备
<!--junit包-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--spring整合junit-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
- 写测试方法
junit就相当于不用走main方法直接执行
//设定类运行器
@RunWith(SpringJUnit4ClassRunner.class)
//配置上下文
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
//测业务层接口
//自动装配
@Autowired
private AccountService accountService;
//写测试方法
@Test
public void testFindById(){
//直接掉方法就可以啦
System.out.println(accountService.findById(2));
}
}
18 AOP
18.1 AOP简介
- 简介
AOP面向切面编程,一种编程规范,指导开发者如何组织程序结构
- 作用
在不惊动原始设计的基础上为其进行功能增强
- 原理
将原来想要其他方法也拥有的功能代码抽取出来,把这块东西抽取出来成method单独的方法
给原来就有的方法取名叫做连接点
给需要追加功能的方法叫做切入点
追加的共性功能的方法(method)中叫做通知
通知方法所在的类叫做通知类
通知和切入点关系叫做切面
18.2 AOP入门案例
- 步骤分析
- 导入AOP坐标(pom.xml)
- 制作连接点方法(原始操作,Dao接口与实现类)
- 制作共性功能(通知类与通知)
- 定义切入点
- 绑定切入点和通知关系(切面)
- pom包导入依赖
导入context的时候,aop的包也自动导入了
导入aspectj的包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
- BookDaoImpl,制作连接点方法
- 接口
public interface BookDao {
public void save();
public void update();
}
- 实现类
@Repository
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println(System.currentTimeMillis());
System.out.println("book dao save ...");
}
public void update(){
System.out.println("book dao update ...");
}
}
- 制作连接点方法以及定义切入点
- 新建一个类advice写这个
//4.通知类必须配置成Spring管理的bean
@Component
//5.设置当前类为切面类类,spring就会把这个当作aop处理
@Aspect
public class MyAdvice {
//2.定义切入点,要求配置在方法上方
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
//3.设置在切入点pt()的前面运行当前操作(前置通知)
@Before("pt()")
//1.共性功能
public void method(){
System.out.println(System.currentTimeMillis());
}
}
- spring配置类也要写一个
@Configuration
@ComponentScan("com.itheima")
//开启注解开发AOP功能,跟@Aspect一起的
@EnableAspectJAutoProxy
public class SpringConfig {
}
- App入口
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = ctx.getBean(BookDao.class);
bookDao.update();
}
}
- 输出结果
1668245211570
book dao save ...
18.3 AOP的工作流程
- 流程
- Spring容器启动
- 读取所有切面配置中的切入点
- 初始化bean,判断bean对应的类中的方法是否匹配到任意切入点
- 匹配失败,创建对象
- 匹配成功,创建原始对象(目标对象)的代理对象
- 执行bean执行方法
- 获取bean,调用方法并执行,完成操作
- 获取bean是代理对象时,根据代理对象的运行模式运行原始方法和增强方法的内容,完成操作
- 实操
- 匹配的上
System.out.println(bookDao);打印出来是System.out.println(bookDao);
System.out.println(bookDao.getClass());打印出来是class com.sun.proxy.$Proxy19
- 匹配不上
System.out.println(bookDao);打印出来是System.out.println(bookDao);
System.out.println(bookDao.getClass());打印出来是class com.itheima.dao.impl.BookDaoImpl
- 总结
打印对象不准确,因为对tostring进行重写了,所以看到的是一个样的
打印.class发现是由代理完成的实现的
18.4 AOP切入点表达式
- 语法格式
-
类型
-
按照接口描述
-
按照接口实现类描述,但一般不用,耦合度高
-
-
切入点表达式标准格式
- 实例
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
@Pointcut("execution(void com.itheima.dao.impl.BookDaoImpl.update())")
- 通配符
- *:表示任意
@Pointcut("execution(* com.itheima.dao.impl.BookDaoImpl.update())")//出
@Pointcut("execution(* com.itheima.dao.impl.BookDaoImpl.update(*))")//不出,因此*表示至少一个
- …:表示多个连续的任意符号,即没有或者多个
@Pointcut("execution(void *..update())")
@Pointcut("execution(* *..*(..))")//表示匹配工程中的所有东西,电脑估计会炸,一般不用
@Pointcut("execution(* *..u*(..))")//u开头的方法
- +:专用于匹配子类类型
- 技巧与规范
18.5 AOP通知类型
- 概念
描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置
- 类型
- 前置通知
- 后置通知
- 环绕通知(重点)
- 返回后通知(了解)
- 抛出异常后通知(了解)
- 运用
- 前置
public class MyAdvice {
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
//@Before:前置通知,在原始方法运行之前执行
@Before("pt()")
public void before() {
System.out.println("before advice ...");
}
}
- 后置
//@After:后置通知,在原始方法运行之后执行
@After("pt()")
public void after() {
System.out.println("after advice ...");
}
- 环绕通知
需要加上对原始操作的调用
//@Around:环绕通知,在原始方法运行的前后执行
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before advice ...");
//表示对原始操作的调用
Object ret = pjp.proceed();
System.out.println("around after advice ...");
return ret;
}
如果接口有返回值,@Around需要返回Object,通过 pjp.proceed()进行返回值的接收
public interface BookDao {
public int select();
}
public class BookDaoImpl implements BookDao {
public int select() {
System.out.println("book dao select is running ...");
// int i = 1/0;
return 100;
}
}
@Around("pt2()")
public Object aroundSelect(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before advice ...");
//表示对原始操作的调用
Integer ret = (Integer) pjp.proceed();
System.out.println("around after advice ...");
return ret;
}
- 返回后通知(了解)
与After的区别是要求原始方法执行过程中未出现异常现象
//@AfterReturning:返回后通知,在原始方法执行完毕后运行,且原始方法执行过程中未出现异常现象
@AfterReturning("pt2()")
public void afterReturning() {
System.out.println("afterReturning advice ...");
}
- 抛出异常后通知
//@AfterThrowing:抛出异常后通知,在原始方法执行过程中出现异常后运行
@AfterThrowing("pt2()")
public void afterThrowing() {
System.out.println("afterThrowing advice ...");
}
18.6 测试业务层接口万次执行效率
- 需求
任意业务层接口执行均可显示其执行效率(执行时长)
- 分析
- 业务功能:业务层接口执行前后分别记录时间,求差值得到执行效率
- 通知类型选择前后均可以增强的类型—环绕通知
- 前情代码
- jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false
jdbc.username=root
jdbc.password=123456
- 配置类JdbcConfig
package com.itheima.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
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;
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
- 配置类MybatisConfig
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
public class MybatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
ssfb.setTypeAliasesPackage("com.itheima.domain");
ssfb.setDataSource(dataSource);
return ssfb;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.itheima.dao");
return msc;
}
}
- spring配置类SpringConfig
import org.springframework.context.annotation.*;
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}
- 实体类Account
package com.itheima.domain;
import java.io.Serializable;
public class Account implements Serializable {
private Integer id;
private String name;
private Double 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 Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
- AccountDao接口(使用mybatis)
public interface AccountDao {
@Insert("insert into tbl_account(name,money)values(#{name},#{money})")
void save(Account account);
@Delete("delete from tbl_account where id = #{id} ")
void delete(Integer id);
@Update("update tbl_account set name = #{name} , money = #{money} where id = #{id} ")
void update(Account account);
@Select("select * from tbl_account")
List<Account> findAll();
@Select("select * from tbl_account where id = #{id} ")
Account findById(Integer id);
}
- AccountService接口
package com.itheima.service;
import com.itheima.domain.Account;
import java.util.List;
public interface AccountService {
void save(Account account);
void delete(Integer id);
void update(Account account);
List<Account> findAll();
Account findById(Integer id);
}
- AccountServiceImpl接口实现类
import java.util.List;
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
public void save(Account account) {
accountDao.save(account);
}
@Override
public void delete(Integer id) {
accountDao.delete(id);
}
@Override
public void update(Account account) {
accountDao.update(account);
}
@Override
public List<Account> findAll() {
return accountDao.findAll() ;
}
@Override
public Account findById(Integer id) {
return accountDao.findById(id);
}
}
- 测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTestCase {
@Autowired
private AccountService accountService;
@Test
public void testFindById(){
Account ac = accountService.findById(2);
System.out.println(ac);
}
@Test
public void testFindAll(){
List<Account> all = accountService.findAll();
System.out.println(all);
}
}
- 进行aop设置
- 打开aop注解
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
@EnableAspectJAutoProxy
public class SpringConfig {
}
- 写aop的类
@Component
//切片类
@Aspect
public class ProjectAdvice {
//匹配业务层的所有方法
@Pointcut("execution(* com.itheima.service.*Service.*(..))")
private void servicePt(){};
@Around("ProjectAdvice.servicePt()")
public void runSpeed(ProceedingJoinPoint pjp) throws Throwable {
//签名信息
Signature signature = pjp.getSignature();
//接口类型
String className = signature.getDeclaringTypeName();
//接口方法
String methodName = signature.getName();
//原始操作前来个记录时间
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
//执行原始操作,不需要返回值了,直接查询10000遍
pjp.proceed();
}
//结束后
long end = System.currentTimeMillis();
System.out.println("万次执行:"+className+"."+methodName+"---->"+(end-start)+"ms");
}
}
- 测试代码
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
public void save(Account account) {
accountDao.save(account);
}
@Override
public void delete(Integer id) {
accountDao.delete(id);
}
@Override
public void update(Account account) {
accountDao.update(account);
}
@Override
public List<Account> findAll() {
return accountDao.findAll() ;
}
@Override
public Account findById(Integer id) {
return accountDao.findById(id);
}
}
- 结果
万次执行:com.itheima.service.AccountService.findAll---->2148ms
万次执行:com.itheima.service.AccountService.findById---->1280ms
18.7 AOP通知获取数据
- 代码
- before
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
private void pt(){}
//JoinPoint:用于描述切入点的对象,必须配置成通知方法中的第一个参数,可用于获取原始方法调用的参数
@Before("pt()")
public void before(JoinPoint jp) {
//用JoinPoint返回参数,返回的是数组
Object[] args = jp.getArgs();
System.out.println(Arrays.toString(args));
System.out.println("before advice ..." );
}
}
- around
正常运行是id为100,可以在中间更改参数,变成600
可以运用到参过来的参数有问题(例如数据格式、数据问题),可以处理
//ProceedingJoinPoint:专用于环绕通知,是JoinPoint子类,可以实现对原始方法的调用,直接getArgs
//@Around("pt()")
public Object around(ProceedingJoinPoint pjp) {
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
//更改args数值,再作为参数扔进去
args[0] = 666;
Object ret = null;
try {
ret = pjp.proceed(args);
} catch (Throwable t) {
t.printStackTrace();
}
return ret;
}
- afterReturning
returning = "ret"与String ret以及JoinPoint和String同时存在
//设置返回后通知获取原始方法的返回值,要求returning属性值必须与方法形参名相同
@AfterReturning(value = "pt()",returning = "ret")
//JoinPoint和String同时存在,JoinPoint得放前面
public void afterReturning(JoinPoint jp,String ret) {
System.out.println("afterReturning advice ..."+ret);
}
- AfterThrowing
throwing = "t"与(Throwable t)
//设置抛出异常后通知获取原始方法运行时抛出的异常对象,要求throwing属性值必须与方法形参名相同,即形参是返回对象
@AfterThrowing(value = "pt()",throwing = "t")
public void afterThrowing(Throwable t) {
System.out.println("afterThrowing advice ..."+t);
}
public String findName(int id,String password) {
System.out.println("id:" + id);
//故意来个异常,给AfterThrowing接收
if (true) throw new NullPointerException();
return "itcast";
}
输出
id:100
afterThrowing advice ...java.lang.NullPointerException
Exception in thread "main" java.lang.NullPointerException
- 总结
- around用proceedingjoinpoint,其他用joinpoint
18.8 案例:百度网盘密码数据兼容处理
- 需求
在业务方法执行之前对所有的输入参数进行格式处理——trim()
- 分析
使用处理后的参数调用原始方法——环绕通知中存在对原始方法的调用
- 前情代码
- maven
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
- 配置类
@Configuration
@ComponentScan("com.itheima")
public class SpringConfig {
}
- ResourcesDao接口
public interface ResourcesDao {
boolean readResources(String url, String password);
}
- ResourcesDaoImpl接口实现类
@Repository
public class ResourcesDaoImpl implements ResourcesDao {
public boolean readResources(String url, String password) {
System.out.println(password.length());
//模拟校验
return password.equals("root");
}
}
- ResourcesService
public interface ResourcesService {
public boolean openURL(String url, String password);
}
- ResourcesServiceImpl接口实现类
@Service
public class ResourcesServiceImpl implements ResourcesService {
@Autowired
private ResourcesDao resourcesDao;
public boolean openURL(String url, String password) {
return resourcesDao.readResources(url,password);
}
}
- App入口
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
ResourcesService resourcesService = ctx.getBean(ResourcesService.class);
boolean flag = resourcesService.openURL("http://pan.baidu.com/haha", "root");
System.out.println(flag);
}
}
- 开工
- 打开aop配置类
@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfig {
}
- DataAdvice类
@Component
@Aspect
public class DataAdvice {
@Pointcut("execution(boolean com.itheima.service.*Service.*(*,*))")
private void servicePt(){}
@Around("com.itheima.aop.DataAdvice.servicePt()")
public Object trimStr(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
for (int i = 0; i < args.length; i++) {
//args是个对象,不一定是字符产,不用foreach循环,用fori,需要用到索引值判断是否是字符串
// 判断参数是不是字符串
if(args[i].getClass().equals(String.class)){
//用toString转成字符串
args[i] = args[i].toString().trim();
}
}
//改完放回去哦
Object ret = pjp.proceed(args);
return ret;
}
}
- 运行App入口类
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
ResourcesService resourcesService = ctx.getBean(ResourcesService.class);
boolean flag = resourcesService.openURL("http://pan.baidu.com/haha", "root ");
System.out.println(flag);
}
}
- 输出结果
4
true
19 Spring事务
19.1 spring事务简介
- 作用
在数据层保障一系列的数据库操作同失败同完成
spring事务作用:在数据层或业务层保障一系列的数据库操作同成功同失败
- 需求
需求:实现任意两个账户间转账操作,A账户减钱,B账户加钱
- JDBC配置
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;
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
- mybatis配置
public class MybatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
ssfb.setTypeAliasesPackage("com.itheima.domain");
ssfb.setDataSource(dataSource);
return ssfb;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.itheima.dao");
return msc;
}
}
- spring配置
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}
- 数据层接口
public interface AccountDao {
@Update("update tbl_account set money = money + #{money} where name = #{name}")
void inMoney(@Param("name") String name, @Param("money") Double money);
@Update("update tbl_account set money = money - #{money} where name = #{name}")
void outMoney(@Param("name") String name, @Param("money") Double money);
}
- 业务层接口
public interface AccountService {
/**
* 转账操作
* @param out 传出方
* @param in 转入方
* @param money 金额
*/
//配置当前接口方法具有事务
@Transactional
public void transfer(String out,String in ,Double money) ;
}
- 业务层实现类
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
public void transfer(String out,String in ,Double money) {
accountDao.outMoney(out,money);
accountDao.inMoney(in,money);
}
}
- 测试用例
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testTransfer() throws IOException {
accountService.transfer("Tom","Jerry",100D);
}
}
- 结果分析
如果中间出现异常,那么会出现A加,B不减的执行问题
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
public void transfer(String out,String in ,Double money) {
accountDao.outMoney(out,money);
int i = 1/0;
accountDao.inMoney(in,money);
}
}
因此在接口中开启事务
@Transactional
public void transfer(String out,String in ,Double money) ;
}
并且在JdbcConfig开启事务管理器
//配置事务管理器,mybatis使用的是jdbc事务,dataSource这个丢进去
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
并且在注解写上事务型驱动
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}
这样运行的时候就会保证同时成功或者失败,抛出异常java.lang.ArithmeticException: / by zero
19.2 Spring事务角色
- 场景
outMoney事务和inMoney事务都会加入到transfer事务中,就可以实现,transfer下的操作同时成功或者失败,不会出现inMoney事务出错,而outMoney事务不回滚的问题了
transfer叫做事务管理员,outMoney事务和inMoney事务叫做事务协调员
事务管理员就是发起事务方,就是发起事务的那个方法,例如transfer
事务协调员就是加入事务方,可以是数据层方法也可以是业务层方法
19.3 事务的配置
- 配置项