【Spring 事务管理系列之二】抛开Spring我们手动实现事务控制

    记得上篇文章,讲述了spring以jdbc作为数据源实现事务控制的列子,可能大家像我一样,觉得原来这样就实现事务啦,对,我一般学习一些新东西,首先会把这个东西能运行的跑出来个demo,不能运行的,我尝试用底层的类,写写代码,以便搞清楚其真实的逻辑,当然学习方法因人而异。

    这一节,准备实现什么呢?我想从最基本的jdbc开始,看看不依赖其他的任何东西,如何实现,事务,这里很简单的讲事务,统一指本地事务,如果牵涉到分布式事务,我会着色标记,这里不考虑事务之间的传播属性,底层数据库的隔离级别,也许以后会有相关的分析,但是目前,我不准备提及。

    好,说了那么多废话,咱们就开始进入正题。这一节,我写应用程序,你分析逻辑,当然如果没人分析的话,我还是要自己分析的,毕竟要引出问题,往下面接着写,写一个系列,一方面和大家分享,一方面作为自己的总结。

    这一次我想验证一个问题,java的事务是决定在谁身上的,上篇博客记得我说过,是Connection。同样以上一小节举得例子,Customer和Address两个表来做说明,本例遵循一般java应用的DAO,Service层的模块组织结构,如果Customer和Address对应的DAO分别获取不同的Connection对象,那么在测试的service中,获取另外一个Connection对象,执行相应的插入操作,如果插入中间出现异常,则即使调用connection回滚,则不起作用,如果按照咱们的预测,那么一定程度上,辅证了上一节的观点。

    模块的组成大致如下:

【Spring 事务管理系列之二】抛开Spring我们手动实现事务控制_第1张图片

关于Customer和Address对应的一般的pojo,不在描述,这里可能有个误区,说你不使用spring的事务管理,干嘛要包含spring呢,其实spring不止有事务的便捷性,还有IOC,我不想手动的创建bean(这个词又坑了多少人啊,理解成包含变量,提供set get方法的类就好,逼格高的名词,我不是很喜欢),ok。

    看完组成之后,我们来看对应的DAO的实现,拿CustomerDAO来说事吧,AddressDAO也类似,代码如下:

public class CustomerDAOImpl implements CustomerDAO {

	private DataSource dataSource;

	/**
	 * @param dataSource
	 */
	public void setDataSource(final DataSource dataSource) {
		this.dataSource = dataSource;
	}

	@Override
	public void create(Customer customer) {
		String queryCustomer = "insert into Customer (id, name) values (?,?)";
		Connection connection =null;
		PreparedStatement preparedStatement = null;
		try {
		    connection = dataSource.getConnection();
		    connection.setAutoCommit(false);
		    
		    preparedStatement = connection.prepareStatement(queryCustomer);
		    preparedStatement.setInt(1, customer.getId());
		    preparedStatement.setString(2, customer.getName());
		    preparedStatement.execute();
		    
		    connection.commit();
		   
        } catch (SQLException e) {
            System.out.println(e);
                try {
                    if(connection!=null){
                        connection.rollback();
                    }
                } catch (SQLException e1) {
                    System.out.println(e1);
                }
        }finally{
            try {
                if(connection!=null){
                    connection.rollback();
                }
            } catch (SQLException e2) {
                System.out.println(e2);
            }
        }
		System.out.println("Inserted into Customer Table Successfully");
	}

}

哈哈,先不要骂我,我一点点说,首先功能是实现了是不是,至于是否优雅,暂且不论,我相信,每个人都写过这样的代码,ok。

在看一下使用这个类的“测试类",为什么测试类打引号呢,因为没有遵从maven的约定,不是在src/main/test目录下建立的测试类,本人比较懒,主测试类的代码:

public static void main(String[] args) {
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
				"spring.xml");
		CustomerDAO customerDAO = (CustomerDAO) ctx.getBean("customerJDBCDAO");
		AddressDAO addressDAO = (AddressDAO) ctx.getBean("addressJDBCDAO");
		Customer cust = createDummyCustomer();
		Address address = createDummyAddress();
		DataSource dataSource = (DataSource) ctx.getBean("dataSource");
		Connection connection =null;
		try {
		    connection = dataSource.getConnection();
		    connection.setAutoCommit(false);
		    
		    customerDAO.create(cust);
		    addressDAO.create(address);
		    
		    connection.commit();
        } catch (Exception e) {
            // TODO: handle exception
            try {
                if(connection!=null)
                    connection.rollback();
            } catch (Exception e2) {
                // TODO: handle exception
            }
        }finally{
            try {
                if(connection!=null)
                    connection.rollback();
            } catch (Exception e2) {
                // TODO: handle exception
            }
        }
		ctx.close();
	}

	private static Customer createDummyCustomer() {
		Customer customer = new Customer();
		customer.setId(2);
		customer.setName("lianzi");
		return customer;
	}
	private static Address createDummyAddress() {
        Address address = new Address();
        address.setId(2);
        address.setCountry("china");
        // setting value more than 20 chars, so that SQLException occurs
        address.setAddress("********************************************");
        return address;
    }

因为使用了spring ioc,所以要把相应的bean的配置加进来,这样你才明白,我为什么没在DAO里面传递DataSource的原因,对应的spring.xml文件的主要配置如下:

<!-- MySQL DB DataSource -->
	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">

		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url" value="jdbc:mysql://localhost:3306/transactionDB" />
		<property name="username" value="root" />
		<property name="password" value="sishao_uestc" />
	</bean>
	<!-- two daos use jdbc datasource -->
	<bean id="customerJDBCDAO" class="com.lianzi.jdbc.dao.impl.CustomerDAOImpl">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	<bean id="addressJDBCDAO" class="com.lianzi.jdbc.dao.impl.AddressDAOImpl">
		<property name="dataSource" ref="dataSource"></property>
	</bean>

好啦,到这里实现啦,运行测试类你会发现,结果和上一节讲的一样,因为AddressDAO里面抛了异常,但是我try catch住啦,也回滚啦,结果如下:

【Spring 事务管理系列之二】抛开Spring我们手动实现事务控制_第2张图片

但是,你会发现,sql数据库里面,结果和上次不一样,customer表里有数据,而address里面没有,这说明回滚不起作用,原因上面说啦,不是同一个Connection,java事务是Connection对象所决定的。

【Spring 事务管理系列之二】抛开Spring我们手动实现事务控制_第3张图片

发现的问题

1、分层带来的困扰

    在业务里面,编写事务处理的代码,污染了业务代码,犯了分层的“忌讳”。

2、把事务说小拉

    你怎么知道,在事务开启的时候,customerDAO和addressDAO在同一个事务里面?妄想的吧

3、代码重复太多

    没写一个DAO,都要开启事务,填充sql,执行sql,把ResultSet解析到bean,或者称之为业务对象

问题的关键点

1、如何界定事务的边界,在哪里声明事务?

2、下一篇文章会重构这部分代码,会因为另外一个问题,这节的例子,事务回滚没有起作用,是因为用的不是一个Connection对象,很简单的想法是使用Connection对象,传递给每个DAO层,那样就会起作用啦,那是这是严重的api污染,于是引出,如何构建一个“上下文”,在事务开始与事务提交时,以及在事务过程中所有数据访问方法都能“隐式”地得到“同一个资源”(数据库连接)?

概念的说明:

1、资源:

无论是jdbc的connecttion、还是hibernate、mybatis的session,统称为资源。

关于资源、事务、线程,也是个很不容易理解的,地方,他们的生命周期是怎么样的呢?

你可能感兴趣的:(【Spring 事务管理系列之二】抛开Spring我们手动实现事务控制)