配色: 字号:
Javaweb Apache的DBUtils框架学习
2016-08-27 | 阅:  转:  |  分享 
  
JavawebApache的DBUtils框架学习



一、commons-dbutils简介



commons-dbutils是Apache组织提供的一个开源JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。因此dbutils成为很多不喜欢hibernate的公司的首选。



commons-dbutilsAPI介绍:



org.apache.commons.dbutils.QueryRunner

org.apache.commons.dbutils.ResultSetHandler

工具类



org.apache.commons.dbutils.DbUtils

二、QueryRunner类使用讲解



该类简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。

QueryRunner类提供了两个构造方法:



默认的构造方法

需要一个javax.sql.DataSource来作参数的构造方法。

2.1、QueryRunner类的主要方法



publicObjectquery(Connectionconn,Stringsql,Object[]params,ResultSetHandlerrsh)throwsSQLException:执行一个查询操作,在这个查询中,对象数组中的每个元素值被用来作为查询语句的置换参数。该方法会自行处理PreparedStatement和ResultSet的创建和关闭。

publicObjectquery(Stringsql,Object[]params,ResultSetHandlerrsh)throwsSQLException:几乎与第一种方法一样;唯一的不同在于它不将数据库连接提供给方法,并且它是从提供给构造方法的数据源(DataSource)或使用的setDataSource方法中重新获得Connection。

publicObjectquery(Connectionconn,Stringsql,ResultSetHandlerrsh)throwsSQLException:执行一个不需要置换参数的查询操作。

publicintupdate(Connectionconn,Stringsql,Object[]params)throwsSQLException:用来执行一个更新(插入、更新或删除)操作。

publicintupdate(Connectionconn,Stringsql)throwsSQLException:用来执行一个不需要置换参数的更新操作。



2.2、使用QueryRunner类实现CRUD



复制代码

1packageme.gacl.test;

2

3importjava.util.Date;

4importjava.util.List;

5importjava.io.File;

6importjava.io.FileReader;

7importjava.io.IOException;

8importjava.sql.SQLException;

9importjavax.sql.rowset.serial.SerialClob;

10importme.gacl.domain.User;

11importme.gacl.util.JdbcUtils;

12importorg.apache.commons.dbutils.QueryRunner;

13importorg.apache.commons.dbutils.handlers.BeanHandler;

14importorg.apache.commons.dbutils.handlers.BeanListHandler;

15importorg.junit.Test;

16

17/

18@ClassName:DBUtilsCRUDTest

19@Description:使用dbutils框架的QueryRunner类完成CRUD,以及批处理

20@author:孤傲苍狼

21@date:2014-10-5下午4:56:44

22

23/

24publicclassQueryRunnerCRUDTest{

25

26/

27测试表

28createtableusers(

29idintprimarykeyauto_increment,

30namevarchar(40),

31passwordvarchar(40),

32emailvarchar(60),

33birthdaydate

34);

35/

36

37@Test

38publicvoidadd()throwsSQLException{

39//将数据源传递给QueryRunner,QueryRunner内部通过数据源获取数据库连接

40QueryRunnerqr=newQueryRunner(JdbcUtils.getDataSource());

41Stringsql="insertintousers(name,password,email,birthday)values(?,?,?,?)";

42Objectparams[]={"孤傲苍狼","123","gacl@sina.com",newDate()};

43//Objectparams[]={"白虎神皇","123","gacl@sina.com","1988-05-07"};

44qr.update(sql,params);

45}

46

47@Test

48publicvoiddelete()throwsSQLException{

49

50QueryRunnerqr=newQueryRunner(JdbcUtils.getDataSource());

51Stringsql="deletefromuserswhereid=?";

52qr.update(sql,1);

53

54}

55

56@Test

57publicvoidupdate()throwsSQLException{

58QueryRunnerqr=newQueryRunner(JdbcUtils.getDataSource());

59Stringsql="updateuserssetname=?whereid=?";

60Objectparams[]={"ddd",5};

61qr.update(sql,params);

62}

63

64@Test

65publicvoidfind()throwsSQLException{

66QueryRunnerqr=newQueryRunner(JdbcUtils.getDataSource());

67Stringsql="selectfromuserswhereid=?";

68Objectparams[]={2};

69Useruser=(User)qr.query(sql,params,newBeanHandler(User.class));

70System.out.println(user.getBirthday());

71}

72

73@Test

74publicvoidgetAll()throwsSQLException{

75QueryRunnerqr=newQueryRunner(JdbcUtils.getDataSource());

76Stringsql="selectfromusers";

77Listlist=(List)qr.query(sql,newBeanListHandler(User.class));

78System.out.println(list.size());

79}

80

81/

82@Method:testBatch

83@Description:批处理

84@Anthor:孤傲苍狼

85

86@throwsSQLException

87/

88@Test

89publicvoidtestBatch()throwsSQLException{

90QueryRunnerqr=newQueryRunner(JdbcUtils.getDataSource());

91Stringsql="insertintousers(name,password,email,birthday)values(?,?,?,?)";

92Objectparams[][]=newObject[10][];

93for(inti=0;i<10;i++){

94params[i]=newObject[]{"aa"+i,"123","aa@sina.com",

95newDate()};

96}

97qr.batch(sql,params);

98}

99

100//用dbutils完成大数据(不建议用)

101/

102createtabletestclob

103(

104idintprimarykeyauto_increment,

105resumetext

106);

107/

108@Test

109publicvoidtestclob()throwsSQLException,IOException{

110QueryRunnerrunner=newQueryRunner(JdbcUtils.getDataSource());

111Stringsql="insertintotestclob(resume)values(?)";//clob

112//这种方式获取的路径,其中的空格会被使用“%20”代替

113Stringpath=QueryRunnerCRUDTest.class.getClassLoader().getResource("data.txt").getPath();

114//将“%20”替换回空格

115path=path.replaceAll("%20","");

116FileReaderin=newFileReader(path);

117char[]buffer=newchar[(int)newFile(path).length()];

118in.read(buffer);

119SerialClobclob=newSerialClob(buffer);

120Objectparams[]={clob};

121runner.update(sql,params);

122}

123}

复制代码

三、ResultSetHandler接口使用讲解



该接口用于处理java.sql.ResultSet,将数据按要求转换为另一种形式。

ResultSetHandler接口提供了一个单独的方法:Objecthandle(java.sql.ResultSet.rs)



3.1、ResultSetHandler接口的实现类



ArrayHandler:把结果集中的第一行数据转成对象数组。

ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。

BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。

BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。

ColumnListHandler:将结果集中某一列的数据存放到List中。

KeyedHandler(name):将结果集中的每一行数据都封装到一个Map里,再把这些map再存到一个map里,其key为指定的key。

MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。

MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List

3.2、测试dbutils各种类型的处理器



复制代码

1packageme.gacl.test;

2

3importjava.sql.SQLException;

4importjava.util.Arrays;

5importjava.util.List;

6importjava.util.Map;

7importme.gacl.util.JdbcUtils;

8importorg.apache.commons.dbutils.QueryRunner;

9importorg.apache.commons.dbutils.handlers.ArrayHandler;

10importorg.apache.commons.dbutils.handlers.ArrayListHandler;

11importorg.apache.commons.dbutils.handlers.ColumnListHandler;

12importorg.apache.commons.dbutils.handlers.KeyedHandler;

13importorg.apache.commons.dbutils.handlers.MapHandler;

14importorg.apache.commons.dbutils.handlers.MapListHandler;

15importorg.apache.commons.dbutils.handlers.ScalarHandler;

16importorg.junit.Test;

17

18/

19@ClassName:ResultSetHandlerTest

20@Description:测试dbutils各种类型的处理器

21@author:孤傲苍狼

22@date:2014-10-6上午8:39:14

23

24/

25publicclassResultSetHandlerTest{

26

27@Test

28publicvoidtestArrayHandler()throwsSQLException{

29QueryRunnerqr=newQueryRunner(JdbcUtils.getDataSource());

30Stringsql="selectfromusers";

31Objectresult[]=(Object[])qr.query(sql,newArrayHandler());

32System.out.println(Arrays.asList(result));//listtoString()

33}

34

35@Test

36publicvoidtestArrayListHandler()throwsSQLException{

37

38QueryRunnerqr=newQueryRunner(JdbcUtils.getDataSource());

39Stringsql="selectfromusers";

40Listlist=(List)qr.query(sql,newArrayListHandler());

41for(Object[]o:list){

42System.out.println(Arrays.asList(o));

43}

44}

45

46@Test

47publicvoidtestColumnListHandler()throwsSQLException{

48QueryRunnerqr=newQueryRunner(JdbcUtils.getDataSource());

49Stringsql="selectfromusers";

50Listlist=(List)qr.query(sql,newColumnListHandler("id"));

51System.out.println(list);

52}

53

54@Test

55publicvoidtestKeyedHandler()throwsException{

56QueryRunnerqr=newQueryRunner(JdbcUtils.getDataSource());

57Stringsql="selectfromusers";

58

59Mapmap=(Map)qr.query(sql,newKeyedHandler("id"));

60for(Map.Entryme:map.entrySet()){

61intid=me.www.wang027.comgetKey();

62Mapinnermap=me.getValue();

63for(Map.Entryinnerme:innermap.entrySet()){

64StringcolumnName=innerme.getKey();

65Objectvalue=innerme.getValue();

66System.out.println(columnName+"="+value);

67}

68System.out.println("----------------");

69}

70}

71

72@Test

73publicvoidtestMapHandler()throwsSQLException{

74

75QueryRunnerqr=newQueryRunner(JdbcUtils.getDataSource());

76Stringsql="selectfromusers";

77

78Mapmap=(Map)qr.query(sql,newMapHandler());

79for(Map.Entryme:map.entrySet())

80{

81System.out.println(me.getKey()+"="+me.getValue());

82}

83}

84

85

86@Test

87publicvoidtestMapListHandler()throwsSQLException{

88QueryRunnerqr=newQueryRunner(JdbcUtils.getDataSource());

89Stringsql="selectfromusers";

90Listlist=(List)qr.query(sql,newMapListHandler());

91for(Mapmap:list){

92for(Map.Entryme:map.entrySet())

93{

94System.out.println(me.getKey()+"="+me.getValue());

95}

96}

97}

98

99@Test

100publicvoidtestScalarHandler()throwsSQLException{

101QueryRunnerqr=newQueryRunner(JdbcUtils.getDataSource());

102Stringsql="selectcount()fromusers";//[13]list[13]

103intcount=((Long)qr.query(sql,newScalarHandler(1))).intValue();

104System.out.println(count);

105}

106}

复制代码

三、DbUtils类使用讲解



DbUtils:提供如关闭连接、装载JDBC驱动程序等常规工作的工具类,里面的所有方法都是静态的。主要方法如下:

publicstaticvoidclose(…)throwsjava.sql.SQLException:DbUtils类提供了三个重载的关闭方法。这些方法检查所提供的参数是不是NULL,如果不是的话,它们就关闭Connection、Statement和ResultSet。

publicstaticvoidcloseQuietly(…):这一类方法不仅能在Connection、Statement和ResultSet为NULL情况下避免关闭,还能隐藏一些在程序中抛出的SQLEeception。

publicstaticvoidcommitAndCloseQuietly(Connectionconn):用来提交连接,然后关闭连接,并且在关闭连接时不抛出SQL异常。

publicstaticbooleanloadDriver(java.lang.StringdriverClassName):这一方装载并注册JDBC驱动程序,如果成功就返回true。使用该方法,你不需要捕捉这个异常ClassNotFoundException。



四、JDBC开发中的事务处理



在开发中,对数据库的多个表或者对一个表中的多条数据执行更新操作时要保证对多个更新操作要么同时成功,要么都不成功,这就涉及到对多个更新操作的事务管理问题了。比如银行业务中的转账问题,A用户向B用户转账100元,假设A用户和B用户的钱都存储在Account表,那么A用户向B用户转账时就涉及到同时更新Account表中的A用户的钱和B用户的钱,用SQL来表示就是:



1updateaccountsetmoney=money-100wherename=''A''

2updateaccountsetmoney=money+100wherename=''B''

4.1、在数据访问层(Dao)中处理事务



对于这样的同时更新一个表中的多条数据的操作,那么必须保证要么同时成功,要么都不成功,所以需要保证这两个update操作在同一个事务中进行。在开发中,我们可能会在AccountDao写一个转账处理方法,如下:



复制代码

1/

2@Method:transfer

3@Description:这个方法是用来处理两个用户之间的转账业务

4在开发中,DAO层的职责应该只涉及到CRUD,

5而这个transfer方法是处理两个用户之间的转账业务的,已经涉及到具体的业务操作,应该在业务层中做,不应该出现在DAO层的

6所以在开发中DAO层出现这样的业务处理方法是完全错误的

7@Anthor:孤傲苍狼

8

9@paramsourceName

10@paramtargetName

11@parammoney

12@throwsSQLException

13/

14publicvoidtransfer(StringsourceName,StringtargetName,floatmoney)throwsSQLException{

15Connectionconn=null;

16try{

17conn=JdbcUtils.getConnection();

18//开启事务

19conn.setAutoCommit(false);

20/

21在创建QueryRunner对象时,不传递数据源给它,是为了保证这两条SQL在同一个事务中进行,

22我们手动获取数据库连接,然后让这两条SQL使用同一个数据库连接执行

23/

24QueryRunnerrunner=newQueryRunner();

25Stringsql1="updateaccountsetmoney=money-100wherename=?";

26Stringsql2="updateaccountsetmoney=money+100wherename=?";

27Object[]paramArr1={sourceName};

28Object[]paramArr2={targetName};

29runner.update(conn,sql1,paramArr1);

30//模拟程序出现异常让事务回滚

31intx=1/0;

32runner.update(conn,sql2,paramArr2);

33//sql正常执行之后就提交事务

34conn.commit();

35}catch(Exceptione){

36e.printStackTrace();

37if(conn!=null){

38//出现异常之后就回滚事务

39conn.rollback();

40}

41}finally{

42//关闭数据库连接

43conn.close();

44}

45}

复制代码

然后我们在AccountService中再写一个同名方法,在方法内部调用AccountDao的transfer方法处理转账业务,如下:



1publicvoidtransfer(StringsourceName,StringtargetName,floatmoney)throwsSQLException{

2AccountDaodao=newAccountDao();

3dao.transfer(sourceName,targetName,money);

4}

上面AccountDao的这个transfer方法可以处理转账业务,并且保证了在同一个事务中进行,但是AccountDao的这个transfer方法是处理两个用户之间的转账业务的,已经涉及到具体的业务操作,应该在业务层中做,不应该出现在DAO层的,在开发中,DAO层的职责应该只涉及到基本的CRUD,不涉及具体的业务操作,所以在开发中DAO层出现这样的业务处理方法是一种不好的设计。



4.2、在业务层(BusinessService)处理事务



由于上述AccountDao存在具体的业务处理方法,导致AccountDao的职责不够单一,下面我们对AccountDao进行改造,让AccountDao的职责只是做CRUD操作,将事务的处理挪到业务层(BusinessService),改造后的AccountDao如下:



复制代码

1packageme.gacl.dao;

2

3importjava.sql.Connection;

4importjava.sql.SQLException;

5importorg.apache.commons.dbutils.QueryRunner;

6importorg.apache.commons.dbutils.handlers.BeanHandler;

7importme.gacl.domain.Account;

8importme.gacl.util.JdbcUtils;

9

10/account测试表

11createtableaccount(

12idintprimarykeyauto_increment,

13namevarchar(40),

14moneyfloat

15)charactersetutf8collateutf8_general_ci;

16

17insertintoaccount(name,money)values(''A'',1000);

18insertintoaccount(name,money)values(''B'',1000);

19insertintoaccount(name,money)values(''C'',1000);

20

21/

22

23/

24@ClassName:AccountDao

25@Description:针对Account对象的CRUD

26@author:孤傲苍狼

27@date:2014-10-6下午4:00:42

28

29/

30publicclassAccountDao{

31

32//接收service层传递过来的Connection对象

33privateConnectionconn=null;

34

35publicAccountDao(Connectionconn){

36this.conn=conn;

37}

38

39publicAccountDao(){

40

41}

42

43/

44@Method:update

45@Description:更新

46@Anthor:孤傲苍狼

47

48@paramaccount

49@throwsSQLException

50/

51publicvoidupdate(Accountaccount)throwsSQLException{

52

53QueryRunnerqr=newQueryRunner();

54Stringsql="updateaccountsetname=?,money=?whereid=?";

55Objectparams[]={account.getName(),account.getMoney(),account.getId()};

56//使用service层传递过来的Connection对象操作数据库

57qr.update(conn,sql,params);

58

59}

60

61/

62@Method:find

63@Description:查找

64@Anthor:孤傲苍狼

65

66@paramid

67@return

68@throwsSQLException

69/

70publicAccountfind(intid)throwsSQLException{

71QueryRunnerqr=newQueryRunner();

72Stringsql="selectfromaccountwhereid=?";

73//使用service层传递过来的Connection对象操作数据库

74return(Account)qr.query(conn,sql,id,newBeanHandler(Account.class));

75}

76}

复制代码

接着对AccountService(业务层)中的transfer方法的改造,在业务层(BusinessService)中处理事务



复制代码

1packageme.gacl.service;

2

3importjava.sql.Connection;

4importjava.sql.SQLException;

5importme.gacl.dao.AccountDao;

6importme.gacl.domain.Account;

7importme.gacl.util.JdbcUtils;

8

9/

10@ClassName:AccountService

11@Description:业务逻辑处理层

12@author:孤傲苍狼

13@date:2014-10-6下午5:30:15

14

15/

16publicclassAccountService{

17

18/

19@Method:transfer

20@Description:这个方法是用来处理两个用户之间的转账业务

21@Anthor:孤傲苍狼

22

23@paramsourceid

24@paramtartgetid

25@parammoney

26@throwsSQLException

27/

28publicvoidtransfer(intsourceid,inttartgetid,floatmoney)throwsSQLException{

29Connectionconn=null;

30try{

31//获取数据库连接

32conn=JdbcUtils.getConnection();

33//开启事务

34conn.setAutoCommit(false);

35//将获取到的Connection传递给AccountDao,保证dao层使用的是同一个Connection对象操作数据库

36AccountDaodao=newAccountDao(conn);

37Accountsource=dao.find(sourceid);

38Accounttarget=dao.find(tartgetid);

39

40source.setMoney(source.getMoney()-money);

41target.setMoney(target.getMoney()+money);

42

43dao.update(source);

44//模拟程序出现异常让事务回滚

45intx=1/0;

46dao.update(target);

47//提交事务

48conn.commit();

49}catch(Exceptione){

50e.printStackTrace();

51//出现异常之后就回滚事务

52conn.rollback();

53}finally{

54conn.close();

55}

56}

57}

复制代码

程序经过这样改造之后就比刚才好多了,AccountDao只负责CRUD,里面没有具体的业务处理方法了,职责就单一了,而AccountService则负责具体的业务逻辑和事务的处理,需要操作数据库时,就调用AccountDao层提供的CRUD方法操作数据库。



4.3、使用ThreadLocal进行更加优雅的事务处理



上面的在businessService层这种处理事务的方式依然不够优雅,为了能够让事务处理更加优雅,我们使用ThreadLocal类进行改造,ThreadLocal一个容器,向这个容器存储的对象,在当前线程范围内都可以取得出来,向ThreadLocal里面存东西就是向它里面的Map存东西的,然后ThreadLocal把这个Map挂到当前的线程底下,这样Map就只属于这个线程了



ThreadLocal类的使用范例如下:



复制代码

1packageme.gacl.test;

2

3publicclassThreadLocalTest{

4

5publicstaticvoidmain(String[]args){

6//得到程序运行时的当前线程

7ThreadcurrentThread=Thread.currentThread();

8System.out.println(currentThread);

9//ThreadLocal一个容器,向这个容器存储的对象,在当前线程范围内都可以取得出来

10ThreadLocalt=newThreadLocal();

11//把某个对象绑定到当前线程上对象以键值对的形式存储到一个Map集合中,对象的的key是当前的线程,如:map(currentThread,"aaa")

12t.set("aaa");

13//获取绑定到当前线程中的对象

14Stringvalue=t.get();

15//输出value的值是aaa

16System.out.println(value);

17}

18}

复制代码

使用使用ThreadLocal类进行改造数据库连接工具类JdbcUtils,改造后的代码如下:



复制代码

1packageme.gacl.util;

2

3importjava.sql.Connection;

4importjava.sql.SQLException;

5importjavax.sql.DataSource;

6importcom.mchange.v2.c3p0.ComboPooledDataSource;

7

8/

9@ClassName:JdbcUtils2

10@Description:数据库连接工具类

11@author:孤傲苍狼

12@date:2014-10-4下午6:04:36

13

14/

15publicclassJdbcUtils2{

16

17privatestaticComboPooledDataSourceds=null;

18//使用ThreadLocal存储当前线程中的Connection对象

19privatestaticThreadLocalthreadLocal=newThreadLocal();

20

21//在静态代码块中创建数据库连接池

22static{

23try{

24//通过代码创建C3P0数据库连接池

25/ds=newComboPooledDataSource();

26ds.setDriverClass("com.mysql.jdbc.Driver");

27ds.setJdbcUrl("jdbc:mysql://localhost:3306/jdbcstudy");

28ds.setUser("root");

29ds.setPassword("XDP");

30ds.setInitialPoolSize(10);

31ds.setMinPoolSize(5);

32ds.setMaxPoolSize(20);/

33

34//通过读取C3P0的xml配置文件创建数据源,C3P0的xml配置文件c3p0-config.xml必须放在src目录下

35//ds=newComboPooledDataSource();//使用C3P0的默认配置来创建数据源

36ds=newComboPooledDataSource("MySQL");//使用C3P0的命名配置来创建数据源

37

38}catch(Exceptione){

39thrownewExceptionInInitializerError(e);

40}

41}

42

43/

44@Method:getConnection

45@Description:从数据源中获取数据库连接

46@Anthor:孤傲苍狼

47@returnConnection

48@throwsSQLException

49/

50publicstaticConnectiongetConnection()throwsSQLException{

51//从当前线程中获取Connection

52Connectionconn=threadLocal.get();

53if(conn==null){

54//从数据源中获取数据库连接

55conn=getDataSource().getConnection();

56//将conn绑定到当前线程

57threadLocal.set(conn);

58}

59returnconn;

60}

61

62/

63@Method:startTransaction

64@Description:开启事务

65@Anthor:孤傲苍狼

66

67/

68publicstaticvoidstartTransaction(){

69try{

70Connectionconn=threadLocal.get();

71if(conn==null){

72conn=getConnection();

73//把conn绑定到当前线程上

74threadLocal.set(conn);

75}

76//开启事务

77conn.setAutoCommit(false);

78}catch(Exceptione){

79thrownewRuntimeException(e);

80}

81}

82

83/

84@Method:rollback

85@Description:回滚事务

86@Anthor:孤傲苍狼

87

88/

89publicstaticvoidrollback(){

90try{

91//从当前线程中获取Connection

92Connectionconn=threadLocal.get();

93if(conn!=null){

94//回滚事务

95conn.rollback();

96}

97}catch(Exceptione){

98thrownewRuntimeException(e);

99}

100}

101

102/

103@Method:commit

104@Description:提交事务

105@Anthor:孤傲苍狼

106

107/

108publicstaticvoidcommit(){

109try{

110//从当前线程中获取Connection

111Connectionconn=threadLocal.get();

112if(conn!=null){

113//提交事务

114conn.commit();

115}

116}catch(Exceptione){

117thrownewRuntimeException(e);

118}

119}

120

121/

122@Method:close

123@Description:关闭数据库连接(注意,并不是真的关闭,而是把连接还给数据库连接池)

124@Anthor:孤傲苍狼

125

126/

127publicstaticvoidclose(){

128try{

129//从当前线程中获取Connection

130Connectionconn=threadLocal.get();

131if(conn!=null){

132conn.close();

133//解除当前线程上绑定conn

134threadLocal.remove();

135}

136}catch(Exceptione){

137thrownewRuntimeException(e);

138}

139}

140

141/

142@Method:getDataSource

143@Description:获取数据源

144@Anthor:孤傲苍狼

145@returnDataSource

146/

147publicstaticDataSourcegetDataSource(){

148//从数据源中获取数据库连接

149returnds;

150}

151}

复制代码

对AccountDao进行改造,数据库连接对象不再需要service层传递过来,而是直接从JdbcUtils2提供的getConnection方法去获取,改造后的AccountDao如下:



复制代码

1packageme.gacl.dao;

2

3importjava.sql.Connection;

4importjava.sql.SQLException;

5importorg.apache.commons.dbutils.QueryRunner;

6importorg.apache.commons.dbutils.handlers.BeanHandler;

7importme.gacl.domain.Account;

8importme.gacl.util.JdbcUtils;

9importme.gacl.util.JdbcUtils2;

10

11/

12createtableaccount(

13idintprimarykeyauto_increment,

14namevarchar(40),

15moneyfloat

16)charactersetutf8collateutf8_general_ci;

17

18insertintoaccount(name,money)values(''A'',1000);

19insertintoaccount(name,money)values(''B'',1000);

20insertintoaccount(name,money)values(''C'',1000);

21

22/

23

24/

25@ClassName:AccountDao

26@Description:针对Account对象的CRUD

27@author:孤傲苍狼

28@date:2014-10-6下午4:00:42

29

30/

31publicclassAccountDao2{

32

33publicvoidupdate(Accountaccount)throwsSQLException{

34

35QueryRunnerqr=newQueryRunner();

36Stringsql="updateaccountsetname=?,money=?whereid=?";

37Objectparams[]={account.getName(),account.getMoney(),account.getId()};

38//JdbcUtils2.getConnection()获取当前线程中的Connection对象

39qr.update(JdbcUtils2.getConnection(),sql,params);

40

41}

42

43publicAccountfind(intid)throwsSQLException{

44QueryRunnerqr=newQueryRunner();

45Stringsql="selectfromaccountwhereid=?";

46//JdbcUtils2.getConnection()获取当前线程中的Connection对象

47return(Account)qr.query(JdbcUtils2.getConnection(),sql,id,newBeanHandler(Account.class));

48}

49}

复制代码

对AccountService进行改造,service层不再需要传递数据库连接Connection给Dao层,改造后的AccountService如下:



复制代码

1packageme.gacl.service;

2

3importjava.sql.SQLException;

4importme.gacl.dao.AccountDao2;

5importme.gacl.domain.Account;

6importme.gacl.util.JdbcUtils2;

7

8publicclassAccountService2{

9

10/

11@Method:transfer

12@Description:在业务层处理两个账户之间的转账问题

13@Anthor:孤傲苍狼

14

15@paramsourceid

16@paramtartgetid

17@parammoney

18@throwsSQLException

19/

20publicvoidtransfer(intsourceid,inttartgetid,floatmoney)throwsSQLException{

21try{

22//开启事务,在业务层处理事务,保证dao层的多个操作在同一个事务中进行

23JdbcUtils2.startTransaction();

24AccountDao2dao=newAccountDao2();

25

26Accountsource=dao.find(sourceid);

27Accounttarget=dao.find(tartgetid);

28source.setMoney(source.getMoney()-money);

29target.setMoney(target.getMoney()+money);

30

31dao.update(source);

32//模拟程序出现异常让事务回滚

33intx=1/0;

34dao.update(target);

35

36//SQL正常执行之后提交事务

37JdbcUtils2.commit();

38}catch(Exceptione){

39e.printStackTrace();

40//出现异常之后就回滚事务

41JdbcUtils2.rollback();

42}finally{

43//关闭数据库连接

44JdbcUtils2.close();

45}

46}

47}

复制代码

这样在service层对事务的处理看起来就更加优雅了。ThreadLocal类在开发中使用得是比较多的,程序运行中产生的数据要想在一个线程范围内共享,只需要把数据使用ThreadLocal进行存储即可。



4.4、ThreadLocal+Filter处理事务



上面介绍了JDBC开发中事务处理的3种方式,下面再介绍的一种使用ThreadLocal+Filter进行统一的事务处理,这种方式主要是使用过滤器进行统一的事务处理,如下图所示:







1、编写一个事务过滤器TransactionFilter



代码如下:



复制代码

1packageme.gac.web.filter;

2

3importjava.io.IOException;

4importjava.sql.Connection;

5importjava.sql.SQLException;

6importjavax.servlet.Filter;

7importjavax.servlet.FilterChain;

8importjavax.servlet.FilterConfig;

9importjavax.servlet.ServletException;

10importjavax.servlet.ServletRequest;

11importjavax.servlet.ServletResponse;

12importjavax.servlet.http.HttpServletRequest;

13importjavax.servlet.http.HttpServletResponse;

14importme.gacl.util.JdbcUtils;

15

16/

17@ClassName:TransactionFilter

18@Description:ThreadLocal+Filter统一处理数据库事务

19@author:孤傲苍狼

20@date:2014-10-6下午11:36:58

21

22/

23publicclassTransactionFilterimplementsFilter{

24

25@Override

26publicvoidinit(FilterConfigfilterConfig)throwsServletException{

27

28}

29

30@Override

31publicvoiddoFilter(ServletRequestrequest,ServletResponseresponse,

32FilterChainchain)throwsIOException,ServletException{

33

34Connectionconnection=null;

35try{

36//1、获取数据库连接对象Connection

37connection=JdbcUtils.getConnection();

38//2、开启事务

39connection.setAutoCommit(false);

40//3、利用ThreadLocal把获取数据库连接对象Connection和当前线程绑定

41ConnectionContext.getInstance().bind(connection);

42//4、把请求转发给目标Servlet

43chain.doFilter(request,response);

44//5、提交事务

45connection.commit();

46}catch(Exceptione){

47e.printStackTrace();

48//6、回滚事务

49try{

50connection.rollback();

51}catch(SQLExceptione1){

52e1.printStackTrace();

53}

54HttpServletRequestreq=(HttpServletRequest)request;

55HttpServletResponseres=(HttpServletResponse)response;

56//req.setAttribute("errMsg",e.getMessage());

57//req.getRequestDispatcher("/error.jsp").forward(req,res);

58//出现异常之后跳转到错误页面

59res.sendRedirect(req.getContextPath()+"/error.jsp");

60}finally{

61//7、解除绑定

62ConnectionContext.getInstance().remove();

63//8、关闭数据库连接

64try{

65connection.close();

66}catch(SQLExceptione){

67e.printStackTrace();

68}

69}

70}

71

72@Override

73publicvoiddestroy(){

74

75}

76}

复制代码

我们在TransactionFilter中把获取到的数据库连接使用ThreadLocal绑定到当前线程之后,在DAO层还需要从ThreadLocal中取出数据库连接来操作数据库,因此需要编写一个ConnectionContext类来存储ThreadLocal,ConnectionContext类的代码如下:



复制代码

1packageme.gac.web.filter;

2

3importjava.sql.Connection;

4

5/

6@ClassName:ConnectionContext

7@Description:数据库连接上下文

8@author:孤傲苍狼

9@date:2014-10-7上午8:36:01

10

11/

12publicclassConnectionContext{

13

14/

15构造方法私有化,将ConnectionContext设计成单例

16/

17privateConnectionContext(){

18

19}

20//创建ConnectionContext实例对象

21privatestaticConnectionContextconnectionContext=newConnectionContext();

22

23/

24@Method:getInstance

25@Description:获取ConnectionContext实例对象

26@Anthor:孤傲苍狼

27

28@return

29/

30publicstaticConnectionContextgetInstance(){

31returnconnectionContext;

32}

33

34/

35@Field:connectionThreadLocal

36使用ThreadLocal存储数据库连接对象

37/

38privateThreadLocalconnectionThreadLocal=newThreadLocal();

39

40/

41@Method:bind

42@Description:利用ThreadLocal把获取数据库连接对象Connection和当前线程绑定

43@Anthor:孤傲苍狼

44

45@paramconnection

46/

47publicvoidbind(Connectionconnection){

48connectionThreadLocal.set(connection);

49}

50

51/

52@Method:getConnection

53@Description:从当前线程中取出Connection对象

54@Anthor:孤傲苍狼

55

56@return

57/

58publicConnectiongetConnection(){

59returnconnectionThreadLocal.get();

60}

61

62/

63@Method:remove

64@Description:解除当前线程上绑定Connection

65@Anthor:孤傲苍狼

66

67/

68publicvoidremove(){

69connectionThreadLocal.remove();

70}

71}

复制代码

在DAO层想获取数据库连接时,就可以使用ConnectionContext.getInstance().getConnection()来获取,如下所示:



复制代码

1packageme.gacl.dao;

2

3importjava.sql.SQLException;

4importorg.apache.commons.dbutils.QueryRunner;

5importorg.apache.commons.dbutils.handlers.BeanHandler;

6

7importme.gac.web.filter.ConnectionContext;

8importme.gacl.domain.Account;

9

10/

11createtableaccount(

12idintprimarykeyauto_increment,

13namevarchar(40),

14moneyfloat

15)charactersetutf8collateutf8_general_ci;

16

17insertintoaccount(name,money)values(''A'',1000);

18insertintoaccount(name,money)values(''B'',1000);

19insertintoaccount(name,money)values(''C'',1000);

20

21/

22

23/

24@ClassName:AccountDao

25@Description:针对Account对象的CRUD

26@author:孤傲苍狼

27@date:2014-10-6下午4:00:42

28

29/

30publicclassAccountDao3{

31

32publicvoidupdate(Accountaccount)throwsSQLException{

33

34QueryRunnerqr=newQueryRunner();

35Stringsql="updateaccountsetname=?,money=?whereid=?";

36Objectparams[]={account.getName(),account.getMoney(),account.getId()};

37//ConnectionContext.getInstance().getConnection()获取当前线程中的Connection对象

38qr.update(ConnectionContext.getInstance().getConnection(),sql,params);

39

40}

41

42publicAccountfind(intid)throwsSQLException{

43QueryRunnerqr=newQueryRunner();

44Stringsql="selectfromaccountwhereid=?";

45//ConnectionContext.getInstance().getConnection()获取当前线程中的Connection对象

46return(Account)qr.query(ConnectionContext.getInstance().getConnection(),sql,id,newBeanHandler(Account.class));

47}

48}

复制代码

businessService层也不用处理事务和数据库连接问题了,这些统一在TransactionFilter中统一管理了,businessService层只需要专注业务逻辑的处理即可,如下所示:



复制代码

1packageme.gacl.service;

2

3importjava.sql.SQLException;

4importme.gacl.dao.AccountDao3;

5importme.gacl.domain.Account;

6

7publicclassAccountService3{

8

9/

10@Method:transfer

11@Description:在业务层处理两个账户之间的转账问题

12@Anthor:孤傲苍狼

13

14@paramsourceid

15@paramtartgetid

16@parammoney

17@throwsSQLException

18/

19publicvoidtransfer(intsourceid,inttartgetid,floatmoney)

20throwsSQLException{

21AccountDao3dao=newAccountDao3();

22Accountsource=dao.find(sourceid);

23Accounttarget=dao.find(tartgetid);

24source.setMoney(source.getMoney()-money);

25target.setMoney(target.getMoney()+money);

26dao.update(source);

27//模拟程序出现异常让事务回滚

28intx=1/0;

29dao.update(target);

30}

31}

复制代码

Web层的Servlet调用businessService层的业务方法处理用户请求,需要注意的是:调用businessService层的方法出异常之后,继续将异常抛出,这样在TransactionFilter就能捕获到抛出的异常,继而执行事务回滚操作,如下所示:



复制代码

1packageme.gacl.web.controller;

2

3importjava.io.IOException;

4importjava.sql.SQLException;

5importjavax.servlet.ServletException;

6importjavax.servlet.http.HttpServlet;

7importjavax.servlet.http.HttpServletRequest;

8importjavax.servlet.http.HttpServletResponse;

9importme.gacl.service.AccountService3;

10

11publicclassAccountServletextendsHttpServlet{

12

13publicvoiddoGet(HttpServletRequestrequest,HttpServletResponseresponse)

14throwsServletException,IOException{

15AccountService3service=newAccountService3();

16try{

17service.transfer(1,2,100);

18}catch(SQLExceptione){

19e.printStackTrace();

20//注意:调用service层的方法出异常之后,继续将异常抛出,这样在TransactionFilter就能捕获到抛出的异常,继而执行事务回滚操作

21thrownewRuntimeException(e);

22}

23}

24

25publicvoiddoPost(HttpServletRequestrequest,HttpServletResponseresponse)

26throwsServletException,IOException{

27doGet(request,response);

献花(0)
+1
(本文系thedust79首藏)