大家多少了解过架构,也听说过使用架构后,代码和可维护性和重用性能大大提升。这里我们来通过一些关于事务的实例,来感性地体会下架构带来的在可维护性方面的便利。本文来是从 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集群等),我们能很方便地通过加设额外的服务器来满足诸如流量增加的需求。

  

  

 

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

  1. SpringMVC内容略多 有用 熟悉基于JSP和Servlet的Java Web开发,对Servlet和JSP的工作原理和生命周期有深入了解,熟练的使用JSTL和EL编写无脚本动态页面,有使用监听器、过滤器等Web组件以及MVC架构模式进行Java Web项目开发的经验。

    熟悉基于JSP和Servlet的Java Web开发,对Servlet和JSP的工作原理和生命周期有深入了解,熟练的使用JSTL和EL编写无脚本动态页面,有使用监听器.过滤器等Web组件以及MVC架构 ...

  2. Java Web架构知识整理——记一次阿里面试经历

    惭愧,从一次电面说起.我个人在某国企做一名软件设计师,国企大家都懂的,待遇一般而且没啥意思,做的方向基本都是操作系统.驱动和工具软件的开发,语言基本都是C/C++.最近也想跳槽,刚好有幸得到了一次阿里 ...

  3. Java Web 三层架构详解

    java 三层架构ssh 一个spring2.5+hibernate3.2+struts2.0组合框架,使用spring的 IoC来管理应用的 所有bean,包括struts2的 action,充分发 ...

  4. REST架构简析(原论文整理)

    0 引言        目前,互联网在社会中扮演的角色越来越重要.通过互联网为广大群众提供服务,也是互联网成功的关键.互联网服务架构目前大多数都是基于REST架构来完成的.REST从它诞生至今,可以说 ...

  5. 读《架构探险——从零开始写Java Web框架》

    内容提要 <架构探险--从零开始写Java Web框架>首先从一个简单的 Web 应用开始,让读者学会如何使用 IDEA.Maven.Git 等开发工具搭建 Java Web 应用:接着通 ...

  6. 大型Java Web项目的架构和部署问题

    一位ID是jackson1225的网友在javaeye询问了一个大型Web系统的架构和部署选型问题,希望能提高现有的基于Java的Web应用的服务能力.由于架构模式和部署调优一直是Java社区的热门话 ...

  7. 从面试官甄别项目经验的角度,说说如何在简历中写项目经验(java后端方向)

    在大多的JD(职位介绍)里,会写明该职位需要xx时间的相关经验,换句话说就是需要在简历中看到一定年限的相关商业项目经验,否则估计连面试的机会都没. 在本文里,不讨论这种门槛是否合理,而会以Java相关 ...

  8. 上海洋码头(www.ymatou.com)急招技术人才(职位:互联网软件开发工程师,.NET网站架构师,Web前端开发工程师,高级测试工程师,产品经理)

    对公司招聘职位有兴趣的童鞋可以把简历发送到zhangzhiqiang@ymatou.com,我们HR会快速给你答复. 互联网软件开发工程师 岗位职责: 1.参与洋码头各个平台(www.ymatou.c ...

  9. Intellij IDEA采用Maven+Spring MVC+Hibernate的架构搭建一个java web项目

    原文:Java web 项目搭建 Java web 项目搭建 简介 在上一节java web环境搭建中,我们配置了开发java web项目最基本的环境,现在我们将采用Spring MVC+Spring ...

随机推荐

  1. C-图文上边对齐

    1.效果 1.1 样式设置 2 效果  2.1 样式

  2. SqlServer和Oracle中一些常用的sql语句4 局部/全局变量

    --把wh1仓库号中姓名含有"平"字的职工工资在原来的基础上加288 update 职工备份 set 工资=工资+288 where 仓库号='wh1' and 姓名 like ' ...

  3. 最火的Android开源项目(一)

    摘要:对于开发者而言,了解当下比较流行的开源项目很是必要.利用这些项目,有时能够让你达到事半功倍的效果.为此,CSDN特整理了GitHub上最受欢迎的Android及iOS开源项目,本文详细介绍了20 ...

  4. 关于extjs表单布局的几种方式

    一.用column布局 layout:'column', defaults:{ style:'float:left;margin:4px;', columnWidth: 0.49, msgTarget ...

  5. 【笔记】【VSCode】Windows下VSCode编译调试c/c++

    转载自http://m.2cto.com/kf/201606/516207.html 首先看效果 设置断点,变量监视,调用堆栈的查看: 条件断点的使用: 下面是配置过程: 总体流程: 下载安装vsco ...

  6. Web安全学习笔记(一)

    Web安全学习笔记(一): URL协议 HTTP协议 1. URL 2. HTTP协议 什么是HTTP HTTP的工作原理 HTTP报文 什么是Cookies HTTP请求方式 Referer请求的功 ...

  7. UVa225,Golygons

    刘儒家翻译的走出的图形可以自交,不知道大家是怎么理解的,反正我是认为这句话的意思是告诉我允许一个点访问多次 这样是WA的,n=15和n=16时多输出很多数据,应该是不允许自交,也就是不允许一个点访问多 ...

  8. java的windows自动化-自动运行java程序

    那么在一些工具齐全并且已经有了一定的写好的java程序的情况下(环境变量和软件见上一章http://www.cnblogs.com/xuezhezlr/p/7718273.html),如何自动化运行j ...

  9. [在线Demo]使用Hibernate多租户实现SaaS服务

    上一篇文章 基于Hibernate实现多租户(Multi-Tendency)功能简单介绍了利用Hibernate的多租户功能提供SaaS服务的方法,但其中有很多不足,后来都得到了解决. 我尝试过抽取实 ...

  10. 237. Delete Node in a Linked List(leetcode)

    Write a function to delete a node (except the tail) in a singly linked list, given only access to th ...