首先发现问题,然后解决问题,最后优化解决办法
从一个简单的转账业务来带入到这个问题中来
- 首先转出账户中钱数会减少,然后转入账户钱会相应增加
- 如果在这两步执行的过程中出现异常,就可能发生问题(转出钱减少了,但是转入没有增加)
代码实现转账业务:
POJO:
@Data
public class Account {
private Integer id;
private String name;
private Double money;
}
Mapper:
public interface IAccountMapper {
@Update("update accounts set money = #{money} where name = #{name}")
int update(Account account);
@Select("select * from accounts where name=#{name}")
Account findByName(String name);
}
Service:
public interface IAccountService {
//转账功能
void transfer(String name1,String name2,Double money);
}
Service实现类:
@Service
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountMapper accountMapper;
@Override
public void transfer(String name1, String name2, Double money) {
// 获得转账两人信息
Account source = accountMapper.findByName(name1);
Account target = accountMapper.findByName(name2);
source.setMoney(source.getMoney()-money);
target.setMoney(target.getMoney()+money);
// 执行source减少和target增加的更新操作
accountMapper.update(source);
// 人为制造一个异常
//System.out.println(10/0);
accountMapper.update(target);
System.out.println("转账业务完成");
}
}
配置文件就省略了。
@Configuration
@ComponentScan({"com.zxy"})
@Import({MybatisConfig.class,DataSourceConfig.class})
public class SpringConfig {
}
现在来进行测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration( classes = {SpringConfig.class})
public class Test1 {
@Autowired
@Qualifier("accountServiceImpl")
private IAccountService accountService;
@Test
public void test1(){
// 张三转账1000给李四
accountService.transfer("zhangsan","lisi",1000.0);
}
}
当我们在业务类中 人为制造一个异常 就会发现:没有保证事务的情况下,张三的钱减少了,但是李四并不会增加,程序出现异常结束。
目前的解决方案:
此时我们的代码并不能手动控制事务,要达到事务的效果,一定不可缺少的就是Connection对象,
Spring中也提供了事务同步管理器,(它使用ThreadLocal,将Connection对象和线程绑定,所以能够保证改事务中的Connection对象是同一个)
DataSourceUtils工具类帮助我们获得该线程中的Connection对象
配置类中增加:
@Bean
public Connection createConnection(@Qualifier("druidDataSource") DataSource dataSource){
TransactionSynchronizationManager.initSynchronization();
Connection connection = DataSourceUtils.getConnection(dataSource);
return connection;
}
不要导错包(import org.springframework.jdbc.datasource.DataSourceUtils; import org.springframework.transaction.support.TransactionSynchronizationManager;
)
自定义事务类:
@Component
public class MyTransactionManager {
@Autowired
private Connection connection;
public void begin() {
try {
// 事务开始,关闭自动提交事务
connection.setAutoCommit(false);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
public void commit(){
try {
// 提交事务
connection.commit();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
public void rollback(){
try {
// 回滚事务
connection.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
public void close(){
try {
// 关闭连接
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
修改业务类:
@Service
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountMapper accountMapper;
@Autowired
private MyTransactionManager transactionManager;// 引入自定义事务类
@Override
public void transfer(String name1, String name2, Double money) {
try {
transactionManager.begin();
// 获得转账两人信息
Account source = accountMapper.findByName(name1);
Account target = accountMapper.findByName(name2);
source.setMoney(source.getMoney() - money);
target.setMoney(target.getMoney() + money);
// 执行source减少和target增加的更新操作
accountMapper.update(source);
// 制造异常
System.out.println(10 / 0);
accountMapper.update(target);
transactionManager.commit();// 未发生异常,提交事务
System.out.println("转账业务完成");
}catch (Exception e){
transactionManager.rollback();// 发生异常,回滚事务
e.printStackTrace();
}finally {
transactionManager.close();// 关闭connection
}
}
}
此时在发生异常时,会回滚事务,就不会出现数据的不一致性
总结:
我们解决了转账中事务问题,但是我们可以发现:我们的为了保证事务,改进了Service中的代码,现在只是一个转账的功能,如果所有业务都需要事务同步,那么上面的代码我们要在每一个方法中都要写一遍(开启事务、提交、回滚、关闭···),每写一个方法都要写重复的事务代码,很明显这不是一个合适的办法。现在我们可以使用代理模式来进一步改进的我们的代码
代理模式
使用一个代理,将原本对象包装起来,然后使用该代理对象“取代”原始对象,任何对原始对象的调用都要通过代理,代理对象决定是否、何时将方法调用转换到原始对象上,从而调用的每一个方法都会经过代理类的包装(或者说是加强)
使用JDK官方的Proxy来实现动态代理
增加一个AccountServiceImpl的代理类
@Component
public class ProxyAccountServiceFactory {
@Autowired
@Qualifier("accountServiceImpl")
private IAccountService accountService;
@Autowired
private MyTransactionManager transactionManager;
@Bean("accountServiceProxy")
public IAccountService createAccountServiceProxy(){
IAccountService accountServiceProxy = (IAccountService) Proxy.newProxyInstance(
accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object rtValue = null;
try {
transactionManager.begin();// 开启事务
// ※※※执行原本业务类中的方法
rtValue = method.invoke(accountService,args);
transactionManager.commit();// 提交事务
}catch (Exception e){
transactionManager.rollback();// 回滚事务
System.out.println("发生异常,事务已回滚--"+e);
}finally {
transactionManager.close();// 关闭连接
}
return rtValue;
}
}
);
return accountServiceProxy;
}
}
原本的AccountServiceImpl类还是回到初始无法处理异常的状态。
测试类调用代理类来完成业务:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration( classes = {SpringConfig.class})
public class Test1 {
@Autowired
@Qualifier("accountServiceProxy")// **修改为代理类**
private IAccountService accountService;
@Test
public void test1(){
accountService.transfer("zhangsan","lisi",100.0);
}
}
补充:
-
Proxy.newProxyInstance返回某一个对象的代理对象
-
需要参数:
- ClassLoader loader:类加载器
- Class<?>[] interfaces:类实现的全部接口
- InvocationHandler h:方法拦截器
-
实现过程:
- 生成一个实现了interfaces里所有接口并且继承了Proxy的代理类的字节码,然后使用loader加载这个代理类
- 使用代理类父类的构造构造函数Proxy来创造一个代理类的实例,将我们自定义的h传入
- 我们的h(例子中的匿名内部类)重写了invoke方法,所以代理类调用每一个方法时都会经过invoke方法,从而完成了对每一个原方法的包装
- 因为代理类实现了interfaces接口,所以返回的实例可以直接强转为接口类型
-
注意:
- 被代理的类必须实现了接口,因为动态代理类在实现的时候继承了Proxy类,java不支持多继承,因此动态代理类只能根据接口来定义方法
使用第三方包CGLIB实现动态代理
导入依赖:
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
编写CglibAccountServiceFactory代理类
@Component
public class CglibAccountServiceFactory {
@Autowired
@Qualifier("accountServiceImpl")
private IAccountService accountService;
@Autowired
private MyTransactionManager transactionManager;
@Bean("accountServiceCglib")
public IAccountService createAccountServiceProxy(){
IAccountService accountServiceCglib =(IAccountService) Enhancer.create(
accountService.getClass(),
new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object rtValue = null;
try {
transactionManager.begin();// 开启事务
rtValue = method.invoke(accountService,objects);
transactionManager.commit();// 提交事务
}catch (Exception e){
transactionManager.rollback();// 回滚事务
System.out.println("发生异常,事务已回滚--"+e);
}finally {
transactionManager.close();// 关闭连接
}
return rtValue;
}
}
);
return accountServiceCglib;
}
}
修改测试类中使用的AccountService:
@Autowired
@Qualifier("accountServiceCglib")
private IAccountService accountService;
补充:
-
Enhancer.create:创建一个类的代理对象
-
参数:
- Class type:类型
- Callback callback:方法拦截器
-
注意:
- 如果委托类被 final 修饰, 那么它不可被代理; 如果委托类中存在被 final 修饰的方法, 那么该方法也不可被代理。
JDK中的Proxy和CGLib对比:
使用JDK代理需要提供interfaces列表(代理类实现接口)
使用CGLib需要提供superclass(代理类继承父类)
JDK代理只能对实现了接口的类生成代理
补充:
CGLib大多是对字节码的操作,适合单例模式,生成的类会放在堆中
JDK1.8后JDK代理的效率就高过了CGLib
Spring中同时使用JDK代理和CGLib,Bean有接口使用JDK代理,没有接口使用CGLib
如果有需要请继续阅读:下一篇Spring中的AOP ※