• 【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获取数据库连接呢?

    略。。。

  • 相关阅读:
    类库探源——开头
    码的生产——(二维码、条形码)
    java 接口
    Eclipse下修改工程名
    Oracle 查询库中所有表名、字段名、字段名说明,查询表的数据条数、表名、中文表名、
    oracle中SQL根据生日日期查询年龄的方法
    TRUNCATE 删除表,无法回退。默认选择为整个表的内容,所以不能加条件。
    【kettle】window安装与配置
    SQL SELECT语句
    Oracle 查询类似 select top 的用法
  • 原文地址:https://www.cnblogs.com/cutter-point/p/9147854.html
Copyright © 2020-2023  润新知