【sping揭秘】22、事务管理

有关事务的楔子

什么是事务???

事务就是以可控的方式对数据资源进行访问的一组操作。

事务本身持有四个限定属性

原子性,一致性,隔离性,持久性

事务家族

Resource Manager  RM,负责存储并管理系统数据资源的状态。数据库服务器、JMS消息服务器等都是。

Transaction Processing Monitor。 TPM或者 TP Monitor, 分布式事务场景中协调多个RM的事务处理。

Transaction Manager。TM 是TP Monitor的核心模块,直接负责多RM之间事务处理的协调工作,并且提供事务界定、事务上下文传播等功能接口。

Application。 事务边界的触发点

还可以根据事务的多寡区分:

全局事务和局部事务

全局事务牵扯多个RM参与,那么需要TP Monitor进行协调,可以称之为 分布式事务  来来,划重点

局部事务

 

群雄逐鹿下的java事务管理

使用JTA或者JCA提供支持

这里插播一条广告:

对于threadlocal大家了解多少呢?

ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。

  在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。

  而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

大家可以去

https://blog.csdn.net/lufeng20/article/details/24314381 看看,还不错

使用spring进行事务管理

编程式事务管理

直接使用platformtransactionManager进行编程式事务管理

这里我们封装一下jdbc的事务操作

package cn.cutter.start.transaction;

import java.sql.Connection;
import java.sql.SQLException;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.transaction.CannotCreateTransactionException;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.transaction.UnexpectedRollbackException;
import org.springframework.transaction.support.DefaultTransactionStatus;

/**
 *
 * spring 架构中对事务的核心接口对于jdbc的实现
 * @author xiaof
 *
 */
@Component
public class JdbcTransactionManager implements PlatformTransactionManager {
    
    @Autowired
    @Qualifier("liferayDataSource1")
    private DataSource dataSource;
    
    public JdbcTransactionManager() {
    }
    
    public JdbcTransactionManager(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    public TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
        
        Connection connection;
        
        try {
            //获取数据源的链接对象
            connection = dataSource.getConnection();
            connection.setAutoCommit(false);
            //绑定当前链接到当前线程
            TransactionResourceManager.bindResource(connection);
            //返回默认事务状态对象
            return new DefaultTransactionStatus(connection, true, true, false, true, null);            
            
        } catch (SQLException e) {
            throw new CannotCreateTransactionException("当前事务无法获取链接", e);
        }
        
    }

    @Override
    public void commit(TransactionStatus status) throws TransactionException {

        //获取事务链接对象,从当前线程threadlocal对象中获取,并且在获取之后要解绑,也就是提交之后,线程解绑事务
        Connection connection = (Connection) TransactionResourceManager.unbindResource();
        //获取链接之后提交
        try {
            connection.commit();
        } catch (SQLException e) {
            throw new TransactionSystemException("提交事务失败", e);
        } finally {
            try {
                connection.close(); //关闭连接,释放资源
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
    }

    @Override
    public void rollback(TransactionStatus status) throws TransactionException {
        
        //事务回滚,,事务回滚的时候,我们也要进行事务对象和当前线程的解绑
        Connection connection = (Connection) TransactionResourceManager.unbindResource();
        
        try {
            connection.rollback();
        } catch (SQLException e) {
            throw new UnexpectedRollbackException("回滚事务失败", e);
        } finally {
            try {
                connection.close();//不管如何,切记,连接资源一定要释放啊啊啊啊啊啊啊啊啊啊!!!!!!!!
            } catch (SQLException e) {
                e.printStackTrace();
            } 
        }
        
    }

}

这里注意了,开启事务管理的核心,就是不能设置自动提交

Spring事务处理

@Test
    public void testTransactionManager() {

        ApplicationContext ctx = this.before();

        // 循环向数据库插入数据
        String sql = "insert into multipleDataSourceTestTable values (?, ?)";
        PlatformTransactionManager transactionManager = (PlatformTransactionManager) ctx.getBean("jdbcTransactionManager");
        DefaultTransactionDefinition definition = null;
        TransactionStatus txStatus = null;
        
        
        try {
            
            // 创建事务对象
            definition = new DefaultTransactionDefinition();
            definition.setTimeout(20);
            
            txStatus = transactionManager.getTransaction(definition);
            
            
            for (int i = 0; i < 10; ++i) {
                // 获取相应的数据连接 模拟项目中不同的业务场景获取jdbctemplate对象
                // JdbcTemplate jdbcTemplate = (JdbcTemplate)
                // ctx.getBean("multipleJdbcTemplate");
                JdbcTemplate jdbcTemplate = new JdbcTemplate((DataSource) ctx.getBean("liferayDataSource1"));
                
                DataVo dataVo = new DataVo();
                dataVo.setNum(i);

                jdbcTemplate.update(sql, new PreparedStatementSetter() {
                    @Override
                    public void setValues(PreparedStatement ps) throws SQLException {
                        ps.setInt(1, dataVo.getNum());
                        ps.setString(2, dataVo.getName());
                    }
                });

                if (i == 7) {
                    throw new Exception("测试");
                }

            }
            
            transactionManager.commit(txStatus);
        } catch (DataAccessException e) {
            transactionManager.rollback(txStatus);
            e.printStackTrace();
        } catch (Exception e) {
            transactionManager.rollback(txStatus);
            e.printStackTrace();
        } finally {
            transactionManager.rollback(txStatus);
        }

    }

这里就是测试,在添加到第7个的时候,我们直接抛出异常,进行事务回滚,结果应是一条都没有插入进去

使用TransactionTemplate进行编程式事务管理

这个其实就是对platformTransactionManager的相关事务操作进行事务模板化封装

Spring对TransactionTemplate提供2个callback接口

分别是:TransactionCallback,TransactionCallbackWithoutResult

后面那个是一个抽象类,实现了TransactionCallback接口,吧里面的方法进行转发,就是转而调用本地的方法

 

首先把template加入sprig容器中

package cn.cutter.start.transaction;

import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;

/**
 * spring的transactiontemplate加入spring容器
 * @author xiaof
 *
 */
@Component
public class TransactionTemplate1FactoryBean implements FactoryBean<TransactionTemplate>{

    @Autowired
    @Qualifier("jdbcTransactionManager")
    private PlatformTransactionManager transactionManager;
    
    @Override
    public TransactionTemplate getObject() throws Exception {
        
        //设置template数据对象
        //这里template需要transactionManager对象
        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
        
        return transactionTemplate;
    }

    @Override
    public Class<?> getObjectType() {
        // TODO Auto-generated method stub
        return TransactionTemplate.class;
    }

    public PlatformTransactionManager getTransactionManager() {
        return transactionManager;
    }

    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    
}

测试代码:

@Test
    public void testTransactionTemplate() {
        
        //获取对应的bean对象,然后利用template进行事务操作
        ApplicationContext ac = this.before();
        //获取template
        TransactionTemplate transactionTemplate = (TransactionTemplate) ac.getBean("transactionTemplate1FactoryBean");
        JdbcTemplate jdbcTemplate = (JdbcTemplate) ac.getBean("jdbcTemplate");
        
        String sql = "insert into multipleDataSourceTestTable values (?, ?)";
        
        //进行插入操作
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                // TODO Auto-generated method stub
                //这里进行业务操作,一般是调用manager层,或者dao层的数据
                for (int i = 0; i < 10; ++i) {
                    // 获取相应的数据连接 模拟项目中不同的业务场景获取jdbctemplate对象
                    // JdbcTemplate jdbcTemplate = (JdbcTemplate)
                    // ctx.getBean("multipleJdbcTemplate");
                    DataVo dataVo = new DataVo();
                    dataVo.setNum(i);

                    jdbcTemplate.update(sql, new PreparedStatementSetter() {
                        @Override
                        public void setValues(PreparedStatement ps) throws SQLException {
                            ps.setInt(1, dataVo.getNum());
                            ps.setString(2, dataVo.getName());
                        }
                    });

                    if (i == 7) {
                        //标识业务回滚
                        logger.warn("这里进行业务回滚");
                        status.setRollbackOnly();
                    }

                }
            }
        });
        
    }

进行操作,当到达第七个的时候,我们会进行事务的回滚,吧数据还原

编程创建基于savepoint的嵌套事务

坑爹,这么老的技术了,我现在在网上还找不到对应的jar,我现在用的这个驱动不支持3.0???

如果是Oracle的话要用ojdbc14.jar支持保存点(Savepoint)

但是我这里用的是mysql!!!

这就很尴尬了,我们去mysql的官网下载一波

https://dev.mysql.com

@Test
    public void testTransactionSavepoint() {
        
        //对于事务保存点的测试
        //获取对应的bean对象,然后利用template进行事务操作
        ApplicationContext ac = this.before();
        
//        DataSource dataSource = (DataSource) ac.getBean("iomTestDataSource");
//        TransactionTemplate transactionTemplate = (TransactionTemplate) ac.getBean("transactionTemplateIomTestFactoryBean");
//        
//        JdbcTemplate jdbctemplate = new JdbcTemplate(dataSource); //传入数据源
        
        TransactionTemplate transactionTemplate = (TransactionTemplate) ac.getBean("transactionTemplate1FactoryBean");
        JdbcTemplate jdbctemplate = (JdbcTemplate) ac.getBean("jdbcTemplateFactoryTestBean");
        
//        String sql = "insert into item_check_info(order_id, chk_order_child_code, file_name, chk_result_code, chk_result_name, sheet_name, row_num) values(?, ?, ?, ?, ?, ?, ?)";
        
        String sql = "insert into multipleDataSourceTestTable(num, name) values (?, ?)";
        
        List params = new ArrayList();
        params.add("1");
//        params.add("1");params.add("1");params.add("1"); params.add("1");params.add("1");
        List params2 = new ArrayList();
        params2.add("2");params2.add("2");
//        params2.add("2");params2.add("2");params2.add("2"); params2.add("2");params2.add("2");
        
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                
                //创建事务存放点
                List params0 = new ArrayList();
//                params0.add("0");params0.add("0");params0.add("0");params0.add("0");params0.add("0");
                params0.add("0");params0.add("0");
                
                jdbctemplate.update(sql, params0.toArray());
                
                Object savePointBeforeInsert = status.createSavepoint();
                //循环插入3条数据
                try {
                    for(int i = 0; i < 3; ++i) {
                        params.add(i);
                        jdbctemplate.update(sql, params.toArray());
                        //在我们跳到第二条记录的时候,我们回退
                        if(i == 2) {
                            logger.info("事务抛出异常,回退数据");
                            throw new Exception("回退");
                        }
                    }
                } catch (DataAccessException e) {
                    logger.info("真的异常,目前不处理");
                    e.printStackTrace();
                } catch (Exception e) {
                    //在这里我们进行事务的回退
                    logger.info("开始回退操作,回退到指定位置");
                    status.rollbackToSavepoint(savePointBeforeInsert);
                    //c存放其余数据
                    jdbctemplate.update(sql, params2.toArray());
                } finally {
                    //最后的最后一定要注意释放资源
                    status.setRollbackOnly();
                }
            }
        });
    }

声明式事务管理

主要是想解放数据库操作和事务操作的混杂,吧数据库操作和事务管理操作解耦剥离开来

XML元数据驱动的声明

Spring事务管理之扩展篇

理解并使用threadlocal

这个对象其实也不难理解,这个方法的作用就是把一个对象在不同的线程中存放不同的副本

在多线程中使用connection的时候,我们就可以使用这个,配置多数据源,在不同的线程中更换不同的数据源

package cn.cutter.start.transaction.multiDatasources;

/**
 * 标识数据源的个数
 * @author xiaof
 *
 */
public enum DataSources {
    MAIN,INFO,DBLINK
}

建立数据源和枚举对象的映射关系

package cn.cutter.start.transaction.multiDatasources;

import java.util.HashMap;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

/**
 * 存放不同的数据源的map
 * @author xiaof
 *
 */
@Component
public class DataSourceMap extends HashMap<Object, Object> {
    
    private final Log logger = LogFactory.getLog(DataSourceMap.class);
    
//    @PostConstruct //创建对象之前注入对象数据
    @Autowired
    public void initMapValue(@Qualifier("liferayDataSource1") DataSource mainDataSource, 
                             @Qualifier("liferayDataSource2") DataSource infoDataSource, 
                             @Qualifier("liferayDataSource2") DataSource dblinkDataSource) {
        //初始化数据
        logger.info("初始化多数据源:
1->" + mainDataSource.getClass().getName() + 
                        "
2->" + infoDataSource.getClass().getName() + 
                        "
3->" + dblinkDataSource.getClass().getName());
        this.put(DataSources.MAIN, mainDataSource);
        this.put(DataSources.INFO, infoDataSource);
        this.put(DataSources.DBLINK, dblinkDataSource);
    }
    
}

控制线程切换的时候获取的数据源key

package cn.cutter.start.transaction.multiDatasources;

/**
 * 多数据源进行管理,对于不同的线程绑定不同的数据源
 * @author xiaof
 *
 */
public class DataSourceTypeManager {
    
    //这里通过threadlocal进行管理
    private static final ThreadLocal<DataSources> dsTypes = new ThreadLocal<DataSources>(){
        @Override
        protected DataSources initialValue() {
            return DataSources.MAIN; // 初始化默认是main数据源
        }
    };
    
    public static void set(DataSources dataSourcesType) {
        dsTypes.set(dataSourcesType);
    }
    
    public static DataSources get() {
        return dsTypes.get();
    }
    
    public static void reset() {
        dsTypes.set(DataSources.MAIN);
    }
}

最后我们的数据源分片信息配置

package cn.cutter.start.transaction.multiDatasources;

import java.util.Map;

import javax.annotation.Resource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;

/**
 * 数据源分片
 * @author xiaof
 *
 */
@Component
public class ThreadLocalVariableRountingDataSource extends AbstractRoutingDataSource {
    
    private final Log logger = LogFactory.getLog(ThreadLocalVariableRountingDataSource.class);
    
    /**
     * 初始化数据输入
     */
    @Resource(name="liferayDataSource1")
    public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
    };
    
    @Autowired
    @Qualifier("dataSourceMap")
    public void setTargetDataSources(Map<Object,Object> targetDataSources) {
        logger.info("开始map注入:" + targetDataSources.size());
        super.setTargetDataSources(targetDataSources);
    };

    @Override
    protected Object determineCurrentLookupKey() {
        //获取当前管理对象的数据源
        return DataSourceTypeManager.get();
    }

}

最后使用的时候

@Test
    public void multiThreadlocal() {
        ApplicationContext ac = this.before();
        //获取对象
        ThreadLocalVariableRountingDataSource threadLocalVariableRountingDataSource = (ThreadLocalVariableRountingDataSource) ac.getBean("threadLocalVariableRountingDataSource");
    
        DataSourceMap dataSourceMap = (DataSourceMap) ac.getBean("dataSourceMap");
        //使用数据源,也就是说
        JdbcTemplate jdbcTemplate = new JdbcTemplate(threadLocalVariableRountingDataSource);
        //数据源切换方式,这样不同的数据,可以根据要求切换不同的数据源
        DataSourceTypeManager.set(DataSources.MAIN);
        DataSourceTypeManager.set(DataSources.INFO);
        DataSourceTypeManager.set(DataSources.DBLINK);
        
        
        System.out.println("ok!!");
                
    }

strategy模式在开发过程中的应用(策略模式)

关于策略模式,大家可以去参考我前面的博客,我不是吹,我那博客写的真的是一朵小金花,大家可以收藏一波,我完全不介意

http://www.cnblogs.com/cutter-point/p/5259874.html

我们在使用这个模式的时候,着眼于剥离客户端代码和关注点之间的依赖关系

Spring与JTA背后的奥秘

Spring的分布式事务强调使用JNDI服务获取datasource

 

那么为什么要用JNDI获取数据库连接呢?

略。。。

原文地址:https://www.cnblogs.com/cutter-point/p/9147854.html