从事务角度粗窥架构的可扩展性和可维护性:内容整理自java web轻量级开发面试教程

    大家多少了解过架构,也听说过使用架构后,代码和可维护性和重用性能大大提升。这里我们来通过一些关于事务的实例,来感性地体会下架构带来的在可维护性方面的便利。本文来是从 java web轻量级开发面试教程从摘录的。

1 JDBC中的事务是方法层面的    

    ①通过setAutoCommit,设置非自动提交。在JDBC里,一般默认是自动提交,即有任何增删改的SQL语句都会当场执行。如果大家设置了非自动提交,记得在用好事务后设置回“自动提交”。

    ②在合适的地方用connection.commit()来提交事务。一般是在执行结束时提交。

    ③可以通过connection.rollback()来回滚事务,回滚语句应放在catch里。一般是有异常了,再回滚事务。

    ④Connection提供了setSavepoint和releaseSavepoint两个方法,用它们可以设置和释放保存点,这样rollback就会到指定的位置,而不是事务的开始位置,但不推荐这种做法。

    通过下面的一段代码,我们来看下在JDBC里使用事务的一般步骤。    

try {
        //这里是连接字符串
	connection = DriverManager.getConnection(
			"jdbc:mysql://localhost:3306/class3", "root", "123456");
	if (connection != null) {
            //设置非自动提交,开始用事务的方式提交
            connection.setAutoCommit(false);
            String query = "insert into student values (?,?)";
            pstmt = connection.prepareStatement(query);				
            pstmt.setString(1,"1");
            pstmt.setString(2,"Peter");
            pstmt.addBatch();
            pstmt.setString(1,"2");
  	    pstmt.setString(2,"Mike");
            pstmt.addBatch();
            pstmt.executeBatch();
             //提交事务
             connection.commit();
     	} else {
        	System.out.println("Failed to make connection!");
	}
} catch (SQLException e) {
        //在catch里,一旦出现异常,需要回滚事务
        connection.rollback(); 
        System.out.println("Some of Students were not inserted correctly.");
        e.printStackTrace();
} finally {
	try {
		connection.close();
	} catch (SQLException e) {
        	e.printStackTrace();
	}
}

    其中第7行里,通过setAutoCommit为false的语句,把事务设置成“手动提交”,在第17行的代码里,虽然进行了批量提交,但不会执行,直到第19行,运行了commit后才会把批量提交的数据插入到数据表里。一旦出现异常,会在第25行的catch从句里,通过rollback回滚事务。

    由于这里的事务是作用在“多次插入”的业务上,如果业务变了,不需要事务,那么我们不得不修改这个方法,乃至整个java文件,也就是说,JDBC事务的维护粒度是方法层面的,基本无法重用。

2 Spring中的编程式事务也较难维护

    这里我们要操作的是UserInfo表,通过下面的Mapping文件,我们能看到表的结构。   

1	//省略必要的package和import代码
2	//用Entity和Table来表示所关联的表
3	@Entity
4	@Table(name="userinfo")  
5	public class UserInfo {
6	   //用ID来标识主键
7		@Id
8		@Column(name = "UserID")
9	    private int userID;
10	    //下面字段和属性之间的关联
11		@Column(name = "username")	
12	    private String userName;
13		@Column(name = "pwd")
14	    private String pwd;
15		@Column(name = "usertype")
16	    private String userType;
17		@Transient
18		private int age;
19		//省略必要的get和set方法
20	…	
21	}

    Spring的配置文件ApplicationContext.xml里,除了要配置Hibernate相关的数据库连接外,还加入了针对事务的配置。 

1	<?xml version="1.0" encoding="gb2312"?>
2	<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
3	 "http://www.springframework.org/dtd/spring-beans.dtd">
4	<beans>
5	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" >
6	<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
7	<property name="url" value="jdbc:mysql://localhost:3306/hibernatechart"></property>
8	<property name="username" value="root"></property>
9	<property name="password" value="123456"></property>
10	</bean>
11	<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean" lazy-init="false">
12	<property name="dataSource" ref="dataSource" />
13	<property name="hibernateProperties">
14	<props>
15	<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
16	<prop key="hibernate.show_sql">true</prop>
17	<prop key="hibernate.hbm2ddl.auto">create</prop>
18	</props>
19	</property>
20	<property name="annotatedClasses">
21	<list>
22	<value>Model.UserInfo</value>
23	</list>
24	</property>
25	</bean>

    第11行到第25行,同前文一样定义了SessionFactory这个bean。第12行,在SessionFactory里引入了dataSource,由此可以成功地连接到数据库。第13行到第19行,配置了Hibernate的诸多属性。第20行到第24行,指定了是用UserInfo这个含注解的文件和数据表关联。   

26		<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
27		<property name="sessionFactory" ref="sessionFactory"></property>
28		<property name="dataSource" ref="dataSource"></property>
29		</bean>

    第26行到第29行,指定了Spring的HibernateTransactionManager类作为事务管理器,在第27行和第28行这个事务管理器里加入了SessionFactory和dataSource这两个属性。

    一旦定义了事务管理器,那么在代码里就会有一些针对事务的操作(比如提交或回滚),以后遇到事务时,就都由这个事务管理器来执行。以前经常用JDBC来操作事务,这里是用HibernateTransactionManager来管理事务。

30	<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
31	<property name="transactionManager" ref="transactionManager"></property>
32	<property name="readOnly" value="false"></property>
33	<property name="isolationLevelName" value="ISOLATION_DEFAULT"></property>
34	</bean>
35	</beans>

    第30行到第34行,定义了一个事务模板TransactionTemplate。在第31行,引入了刚才定义的事务管理器。在第32行,定义了这个事务不是只读的。在第33行,定义了这个事务的事务隔离级别。在前面数据库部分章节曾讲过这部分的知识,这里提一下,一般是有5个常量来描述事务隔离级别,级别从低到高分别如下:

①读取未提交:TRANSACTION_READ_UNCOMMITTED

②读取提交:TRANSACTION_READ_COMMITTED

③可重读:TRANSACTION_REPEATABLE_READ

④可串化:TRANSACTION_SERIALIZABLE

    另外还有一个,是不支持事务:TRANSACTION_NONE JDBC

    把事务管理器比作饭店里的厨师,是由它具体地执行事务,而事务模板就好比是菜单。可以在菜单里加入我们需要的配置,随后提交给事务管理器来执行。下面通过HibernateMain这个类来看一下事务的调用过程。   

1	//省略必要的package和import方法
2	public class HibernateMain {
3		private TransactionTemplate transactionTemplate;  
4		SessionFactory sessionFactory = null;
5	    Session session = null;
6		//在这个方法里,我们通过事务来插入两个User
7		private void updateUsers()
8		{
9			ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
10			sessionFactory = (SessionFactory)context.getBean("sessionFactory");
11			session = sessionFactory.openSession();
12		transactionTemplate=(TransactionTemplate)context.getBean("transactionTemplate");
13			transactionTemplate.execute(new TransactionCallback<Boolean>() {
14	            @Override
15	            public Boolean doInTransaction(TransactionStatus ts) {
16		try {            	
17			UserInfo user = new UserInfo();
18			user.setUserID(1);
19			user.setUserName("Mike");
20			user.setPwd("123");
21			user.setUserType("VVip");
22			session.save(user);                    
23			UserInfo anotherUser = new UserInfo();
24			anotherUser.setUserID(2);
25			anotherUser.setUserName("Peter");
26			anotherUser.setPwd("456");
27			anotherUser.setUserType("Vip");
28			session.save(anotherUser);
29			session.flush();
30	                }
31	                catch (Exception e) {
32		System.out.println("Roll Back");
33		ts.setRollbackOnly();
34		e.printStackTrace();
35		return false;
36	                }
37		System.out.println("Correct");
38	                return true;
39	            }			
40	        });
41			session.close();
42			sessionFactory.close();
43		}

    第13行到第40行,在transactionTemplate.execute里放入了和事务有关的代码。具体而言,在第15行的doInTransaction里,向数据表里存放了两个UserInfo的信息。

    如果没有发生异常,那么代码能正确地执行到第40行的位置,在正确退出方法时,会提交事务(把连个UserInfo对象一起插入数据表里)。一旦出现异常,那么会在第31行的catch从句里,通过第33行的代码执行回滚操作,撤销事务。     

44  public static void main(String[] args) {
45	        try{
46		HibernateMain main = new HibernateMain();
47		main.updateUsers();        		
48	        }
49	        catch(Exception e)
50	        {
51		e.printStackTrace();        	
52	        }   
53		}
54	}

    第47行main函数里,调用了updateUsers这个方法,通过这个方法,以事务的方式插入了两个UserInfo对象。

    虽然已经在Spring的配置文件里加入了针对事务的配置,但由于在代码里,显式地把事务操作的代码放入了transactionTemplate.execute这个方法里,所以是编程式事务,具体而言,还是在Java代码里(而不是配置文件里)通过编程的方式使用事务。

   在编程式事务里,数据库操作的逻辑(比如这里是插入两个UserInfo)和事务代码是紧密地耦合在一起的,一旦要修改事务部分的代码(比如以后换事务模板了),那么包含数据库操作的代码(这里是updateUsers方法)就不得不一起跟着修改。

    正是因为有这类问题,所以在当前的项目里,编程式事务用得比较少,大多还是用下面提到的声明式事务。  

3 声明式事务的管理方式

    针对特定的项目,可以在Spring的配置文件里制定一个规则,以此可以指定针对特定类(一般是数据库操作的相关类)的特定方法(一般是涉及事务操作的方法)添加事务控制,并设置好事务的相关属性。

    这样一来,如果单单观察类和方法本身,是看不到任何事务的痕迹(因为耦合度很低),而一旦执行到具体的方法,事务就会起作用。    

    这里还是用刚才的UserInfo数据表,在ApplicationContext.xml这个Spring的配置文件里,不仅配置了连接MySQL数据库的方式,更配置了针对事务的描述,代码如下。   

1	<?xml version="1.0" encoding="UTF-8"?>
2	<beans xmlns="http://www.springframework.org/schema/beans"
3	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
4	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
5	    xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"    xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
6	http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd  
7	            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
8	            http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.2.xsd  
9	            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">

    至此配置了一些本配置文件里会用到的命名空间。   

10	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" >
11	<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
12	<property name="url" value="jdbc:mysql://localhost:3306/hibernatechart"></property>
13	<property name="username" value="root"></property>
14	<property name="password" value="123456"></property>
15	</bean>

    第10行到第15行,在id为dataSource的bean里,配置了连接MySQL数据库的信息。   

16	<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean" lazy-init="false">
17	<property name="dataSource" ref="dataSource" />
18	<property name="hibernateProperties">
19	<props>
20	<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
21	<prop key="hibernate.show_sql">true</prop>
22	<prop key="hibernate.hbm2ddl.auto">create</prop>
23	</props>
24	</property>
25	<property name="annotatedClasses">
26	<list>
27	<value>Model.UserInfo</value>
28	</list>
29	</property>
30	</bean>

    第16行到第30行,在id为sessionFactory的bean里,配置了Hibernate的信息。具体而言,在第17行,指定了该Hibernate需要用到dataSource的配置连接数据库。在第20行到第23行,配置了诸如“是否显示SQL语句”等Hibernate属性。在第27行,指定了是通过UserInfo这个带注解的文件来实现数据表到本地Model对象的映射。     

31	<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
32		<property name="dataSource" ref="dataSource"></property>
33		<property name="sessionFactory" ref="sessionFactory"></property>
34		</bean>	
35		<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
36	<property name="dataSource" ref="dataSource" />
37	</bean>

    第31行到第34行,在id为transactionManager的bean里,配置了事务管理器信息,由此来实现提交和回滚等操作。第35行到第37行,配置了一个封装数据库操作的jdbcTemplate对象。   

38	<tx:annotation-driven transaction-manager="transactionManager"/>
39	<tx:advice id="txAdvice" transaction-manager="transactionManager">
40	<tx:attributes>
41	<tx:method name="update*" propagation="REQUIRED" />
42	<tx:method name="delete*" propagation="REQUIRED" />
43	<tx:method name="add*" propagation="REQUIRED" />
44	<tx:method name="*" propagation="REQUIRED" read-only="false" />
45	</tx:attributes>
46	</tx:advice>

    第38行到第46行,配置了针对事务的一些规则,这也是声明式事务的关键代码。

    具体而言,在第39行,说明了通过transactionManager来实现针对事务的操作。第40行到第45行,说明了事务的应用范围。比如第41行,说明了名字为update…的方法将采用REQUIRED这类管理方式。  

    随后来看下在HibernateMain类里使用事务的方式。   

1	//省略必要的package和import方法
2	public class HibernateMain {
3		private JdbcTemplate jdbcTemplate;  
4	    public JdbcTemplate getJdbcTemplate() {  
5	        return jdbcTemplate;  
6	    }
7	    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {  
8	        this.jdbcTemplate = jdbcTemplate;  
9	    }  
10	    //在这个方法里,我们插入了两个UserInfo的对象
11		private void updateUsers()
12		{
13			ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
14			jdbcTemplate = (JdbcTemplate)context.getBean("jdbcTemplate"); 
15			String sql="INSERT INTO userInfo VALUES(?,?,?,?)";
16			jdbcTemplate.update(sql, "1","Mike","123","VIP");  
17			jdbcTemplate.update(sql, "2","Peter","456","VIP");
18		}	
19		public static void main(String[] args) {
20		  try{
21		HibernateMain main = new HibernateMain();
22	        //这里是调用
23		main.updateUsers();        		
24	       }
25	        catch(Exception e)
26	        {
27		e.printStackTrace();        	
28	        }   
29		}
30	}

    第23行,调用了updateUsers这个方法。在第11行的updateUsers方法里,仅仅是通过jdbcTemplate对象向UserInfo表里插入了两条数据,一点也看不出事务的痕迹。

    但是在ApplicationContext.xml这个配置文件里,已经定义了在com这个包(HibernateMain在这个包里)的所有类里的以update开头的方法都将以propagation="REQUIRED"的方式执行事务,所以事实上在updateUsers方法里,已经用到了事务。

 也就是说,在声明式事务里,数据库操作业务和事务管理的代码是分离的,哪天我们不想再代码里引入事务,无需修改java代码,另外,如果要更改事务的配置,也无需更改java代码,只需更改配置文件即可。

4 关于框架的说明

    在声明式事务里,我们能看到,如果需求变更(比如不再需要事务了),我们只需要更改对应的配置,而且这个修改的范围不会影响到不相干的部分(比如数据库业务部分),这就叫“可维护性”高。

    我们使用框架的目的不是为了好看(客户不会因代码好看而多加钱),正是为了让代码很好地适应各种变更。事实上,Spring MVC框架(或Spring MVC+ORM),抽象了从前端请求到后端处理的流程,所以当在项目里加各种业务时(比如电商项目里加个订单模块),用这类框架能很好地处理这种变更。

    进而言之,通过各种分布式处理框架(广义而言包括MYSQL集群,LVS,或Redis集群等),我们能很方便地通过加设额外的服务器来满足诸如流量增加的需求。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏芋道源码1024

分布式消息队列 RocketMQ源码解析:事务消息

本文主要基于 RocketMQ 4.0.x 正式版 1. 概述 2. 事务消息发送 2.1 Producer 发送事务消息 2.2 Broker 处理结束事务请...

56460
来自专栏菩提树下的杨过

IBM WebSphere MQ 7.5基本用法

一、下载7.5 Trial版本 http://www.ibm.com/developerworks/downloads/ws/wmq/ 这是下载网址,下载前先必...

43380
来自专栏IT笔记

SpringBoot开发案例之整合ActiveMQ实现秒杀队列

在实际生产环境中中,通常生产者和消费者会是两个独立的应用,这样才能通过消息队列实现了服务解耦和广播。因为此项目仅是一个案例,为了方便期间,生产和消费定义在了同一...

2.1K40
来自专栏用户2442861的专栏

使用IntelliJ IDEA开发SpringMVC网站(四)用户管理

转载请注明出处:Gaussic(一个致力于AI研究却不得不兼顾项目的研究生) 。

27610
来自专栏Java面试通关手册

结合Spring发送邮件的四种正确姿势,你知道几种?

测试使用的环境是企业主流的SSM 框架即 SpringMVC+Spring+Mybatis。为了节省时间,我直接使用的是我上次的“SSM项目中整合Echarts...

15230
来自专栏耕耘实录

RHEL7.X系列及周边Linux发行版中,关于MBR与GPT的选择一些思考与建议

存储的选型、规划与管理等工作一直以来都是日常系统运维工作中的重点。MBR与GPT两种类型的分区表的选择与使用则是在磁盘管理中需要根据应用场景来注或考虑的要点。结...

13720
来自专栏Greenplum

Linux 常用命令(四)

# Default runlevel. The runlevels used are:

27900
来自专栏Java Web

MyBatis 与 Spring 整合

MyBatis—Spring 项目 目前大部分的 Java 互联网项目,都是用 Spring MVC + Spring + MyBatis 搭建平台的。 使用 ...

44560
来自专栏青玉伏案

JavaEE开发之记事本完整案例(SpringBoot + iOS端)

上篇博客我们聊了《JavaEE开发之SpringBoot整合MyBatis以及Thymeleaf模板引擎》,并且在之前我们也聊了《Swift3.0服务端开发(五...

23450
来自专栏耕耘实录

RHEL7及CentOS7的语言、字符编码、键盘映射、X11布局设置(localectl)-系统管理(1)

版权声明:本文为耕耘实录原创文章,各大自媒体平台同步更新。欢迎转载,转载请注明出处,谢谢

18920

扫码关注云+社区

领取腾讯云代金券