前言
上一篇Spring博文主要講解了如何使用Spring來實現(xiàn)AOP編程宙帝,本博文主要講解Spring的DAO模塊對JDBC的支持炼吴,以及Spring對事務(wù)的控制...
對于JDBC而言,我們肯定不會陌生计技,我們在初學的時候肯定寫過非常非常多的JDBC模板代碼!
回顧對模版代碼優(yōu)化過程
我們來回憶一下我們怎么對模板代碼進行優(yōu)化的!
- 首先來看一下我們原生的JDBC:需要手動去數(shù)據(jù)庫的驅(qū)動從而拿到對應(yīng)的連接..
try {
String sql = "insert into t_dept(deptName) values('test');";
Connection con = null;
Statement stmt = null;
Class.forName("com.mysql.jdbc.Driver");
// 連接對象
con = DriverManager.getConnection("jdbc:mysql:///hib_demo", "root", "root");
// 執(zhí)行命令對象
stmt = con.createStatement();
// 執(zhí)行
stmt.execute(sql);
// 關(guān)閉
stmt.close();
con.close();
} catch (Exception e) {
e.printStackTrace();
}
- 因為JDBC是面向接口編程的刻撒,因此數(shù)據(jù)庫的驅(qū)動都是由數(shù)據(jù)庫的廠商給做到好了,我們只要加載對應(yīng)的數(shù)據(jù)庫驅(qū)動耿导,便可以獲取對應(yīng)的數(shù)據(jù)庫連接....因此,我們寫了一個工具類态贤,專門來獲取與數(shù)據(jù)庫的連接(Connection),當然啦舱呻,為了更加靈活,我們的工具類是讀取配置文件的方式來做的。
/*
* 連接數(shù)據(jù)庫的driver箱吕,url芥驳,username,password通過配置文件來配置茬高,可以增加靈活性
* 當我們需要切換數(shù)據(jù)庫的時候兆旬,只需要在配置文件中改以上的信息即可
*
* */
private static String driver = null;
private static String url = null;
private static String username = null;
private static String password = null;
static {
try {
//獲取配置文件的讀入流
InputStream inputStream = UtilsDemo.class.getClassLoader().getResourceAsStream("db.properties");
Properties properties = new Properties();
properties.load(inputStream);
//獲取配置文件的信息
driver = properties.getProperty("driver");
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");
//加載驅(qū)動類
Class.forName(driver);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url,username,password);
}
public static void release(Connection connection, Statement statement, ResultSet resultSet) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
- 經(jīng)過上面一層的封裝,我們可以在使用的地方直接使用工具類來得到與數(shù)據(jù)庫的連接...那么比原來就方便很多了怎栽!但是呢丽猬,每次還是需要使用Connection去創(chuàng)建一個Statement對象。并且無論是什么方法熏瞄,其實就是SQL語句和傳遞進來的參數(shù)不同脚祟!
- 于是,我們就自定義了一個JDBC的工具類强饮,詳情可以看http://blog.csdn.net/hon_3y/article/details/53760782#t6
- 我們自定義的工具類其實就是以DbUtils組件為模板來寫的由桌,因此我們在開發(fā)的時候就一直使用DbUtils組件了。
使用Spring的JDBC
上面已經(jīng)回顧了一下以前我們的JDBC開發(fā)了邮丰,那么看看Spring對JDBC又是怎么優(yōu)化的
首先行您,想要使用Spring的JDBC模塊,就必須引入兩個jar文件:
- 引入jar文件
- spring-jdbc-3.2.5.RELEASE.jar
- spring-tx-3.2.5.RELEASE.jar
- 首先還是看一下我們原生的JDBC代碼:獲取Connection是可以抽取出來的剪廉,直接使用dataSource來得到Connection就行了娃循。
public void save() {
try {
String sql = "insert into t_dept(deptName) values('test');";
Connection con = null;
Statement stmt = null;
Class.forName("com.mysql.jdbc.Driver");
// 連接對象
con = DriverManager.getConnection("jdbc:mysql:///hib_demo", "root", "root");
// 執(zhí)行命令對象
stmt = con.createStatement();
// 執(zhí)行
stmt.execute(sql);
// 關(guān)閉
stmt.close();
con.close();
} catch (Exception e) {
e.printStackTrace();
}
}
- 值得注意的是,JDBC對C3P0數(shù)據(jù)庫連接池是有很好的支持的妈经。因此我們直接可以使用Spring的依賴注入淮野,在配置文件中配置dataSource就行了!
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql:///hib_demo"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
<property name="initialPoolSize" value="3"></property>
<property name="maxPoolSize" value="10"></property>
<property name="maxStatements" value="100"></property>
<property name="acquireIncrement" value="2"></property>
</bean>
// IOC容器注入
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public void save() {
try {
String sql = "insert into t_dept(deptName) values('test');";
Connection con = null;
Statement stmt = null;
// 連接對象
con = dataSource.getConnection();
// 執(zhí)行命令對象
stmt = con.createStatement();
// 執(zhí)行
stmt.execute(sql);
// 關(guān)閉
stmt.close();
con.close();
} catch (Exception e) {
e.printStackTrace();
}
}
Spring來提供了JdbcTemplate這么一個類給我們使用吹泡!它封裝了DataSource骤星,也就是說我們可以在Dao中使用JdbcTemplate就行了。
創(chuàng)建dataSource爆哑,創(chuàng)建jdbcTemplate對象
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql:///zhongfucheng"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
<property name="initialPoolSize" value="3"></property>
<property name="maxPoolSize" value="10"></property>
<property name="maxStatements" value="100"></property>
<property name="acquireIncrement" value="2"></property>
</bean>
<!--掃描注解-->
<context:component-scan base-package="bb"/>
<!-- 2\. 創(chuàng)建JdbcTemplate對象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
- userDao
package bb;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
/**
* Created by ozc on 2017/5/10.
*/
@Component
public class UserDao implements IUser {
//使用Spring的自動裝配
@Autowired
private JdbcTemplate template;
@Override
public void save() {
String sql = "insert into user(name,password) values('zhoggucheng','123')";
template.update(sql);
}
}
- 測試:
@Test
public void test33() {
ApplicationContext ac = new ClassPathXmlApplicationContext("bb/bean.xml");
UserDao userDao = (UserDao) ac.getBean("userDao");
userDao.save();
}
JdbcTemplate查詢
我們要是使用JdbcTemplate查詢會發(fā)現(xiàn)有很多重載了query()方法
一般地洞难,如果我們使用queryForMap(),那么只能封裝一行的數(shù)據(jù)揭朝,如果封裝多行的數(shù)據(jù)队贱、那么就會報錯!并且潭袱,Spring是不知道我們想把一行數(shù)據(jù)封裝成是什么樣的柱嫌,因此返回值是Map集合...我們得到Map集合的話還需要我們自己去轉(zhuǎn)換成自己需要的類型。
我們一般使用下面這個方法:
我們可以實現(xiàn)RowMapper屯换,告訴Spriing我們將每行記錄封裝成怎么樣的编丘。
public void query(String id) {
String sql = "select * from USER where password=?";
List<User> query = template.query(sql, new RowMapper<User>() {
//將每行記錄封裝成User對象
@Override
public User mapRow(ResultSet resultSet, int i) throws SQLException {
User user = new User();
user.setName(resultSet.getString("name"));
user.setPassword(resultSet.getString("password"));
return user;
}
},id);
System.out.println(query);
}
當然了与学,一般我們都是將每行記錄封裝成一個JavaBean對象的,因此直接實現(xiàn)RowMapper嘉抓,在使用的時候創(chuàng)建就好了索守。
class MyResult implements RowMapper<Dept>{
// 如何封裝一行記錄
@Override
public Dept mapRow(ResultSet rs, int index) throws SQLException {
Dept dept = new Dept();
dept.setDeptId(rs.getInt("deptId"));
dept.setDeptName(rs.getString("deptName"));
return dept;
}
}
事務(wù)控制概述
下面主要講解Spring的事務(wù)控制,如何使用Spring來對程序進行事務(wù)控制....
- Spring的事務(wù)控制是屬于Spring Dao模塊的抑片。
一般地卵佛,我們事務(wù)控制都是在service層做的。敞斋。為什么是在service層而不是在dao層呢截汪??有沒有這樣的疑問...
service層是業(yè)務(wù)邏輯層渺尘,service的方法一旦執(zhí)行成功挫鸽,那么說明該功能沒有出錯。
一個service方法可能要調(diào)用dao層的多個方法...如果在dao層做事務(wù)控制的話鸥跟,一個dao方法出錯了丢郊,僅僅把事務(wù)回滾到當前dao的功能,這樣是不合適的[因為我們的業(yè)務(wù)由多個dao方法組成]医咨。如果沒有出錯枫匾,調(diào)用完dao方法就commit了事務(wù),這也是不合適的[導(dǎo)致太多的commit操作]拟淮。
事務(wù)控制分為兩種:
- 編程式事務(wù)控制
- 聲明式事務(wù)控制
編程式事務(wù)控制
自己手動控制事務(wù)干茉,就叫做編程式事務(wù)控制。
-
Jdbc代碼:
Conn.setAutoCommite(false); // 設(shè)置手動控制事務(wù)
-
Hibernate代碼:
Session.beginTransaction(); // 開啟一個事務(wù)
【細粒度的事務(wù)控制: 可以對指定的方法很泊、指定的方法的某幾行添加事務(wù)控制】
(比較靈活角虫,但開發(fā)起來比較繁瑣: 每次都要開啟、提交委造、回滾.)
聲明式事務(wù)控制
Spring提供對事務(wù)的控制管理就叫做聲明式事務(wù)控制
Spring提供了對事務(wù)控制的實現(xiàn)戳鹅。
- 如果用戶想要使用Spring的事務(wù)控制,只需要配置就行了昏兆。
- 當不用Spring事務(wù)的時候枫虏,直接移除就行了。
- Spring的事務(wù)控制是基于AOP實現(xiàn)的爬虱。因此它的耦合度是非常低的隶债。
- 【粗粒度的事務(wù)控制: 只能給整個方法應(yīng)用事務(wù),不可以對方法的某幾行應(yīng)用事務(wù)跑筝。】
- (因為aop攔截的是方法死讹。)
Spring給我們提供了事務(wù)的管理器類,事務(wù)管理器類又分為兩種曲梗,因為JDBC的事務(wù)和Hibernate的事務(wù)是不一樣的回俐。
- Spring聲明式事務(wù)管理器類:
Jdbc技術(shù):DataSourceTransactionManager
Hibernate技術(shù):HibernateTransactionManager
聲明式事務(wù)控制
我們基于Spring的JDBC來做例子吧
引入相關(guān)jar包
- AOP相關(guān)的jar包【因為Spring的聲明式事務(wù)控制是基于AOP的逛腿,那么就需要引入AOP的jar包〗銎模】
- 引入tx名稱空間
- 引入AOP名稱空間
- 引入jdbcjar包【jdbc.jar包和tx.jar包】
搭建配置環(huán)境
- 編寫一個接口
public interface IUser {
void save();
}
- UserDao實現(xiàn)類,使用JdbcTemplate對數(shù)據(jù)庫進行操作碘举!
@Repository
public class UserDao implements IUser {
//使用Spring的自動裝配
@Autowired
private JdbcTemplate template;
@Override
public void save() {
String sql = "insert into user(name,password) values('zhong','222')";
template.update(sql);
}
}
- userService
@Service
public class UserService {
@Autowired
private UserDao userDao;
public void save() {
userDao.save();
}
}
- bean.xml配置:配置數(shù)據(jù)庫連接池忘瓦、jdbcTemplate對象、掃描注解
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--數(shù)據(jù)連接池配置-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql:///zhongfucheng"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
<property name="initialPoolSize" value="3"></property>
<property name="maxPoolSize" value="10"></property>
<property name="maxStatements" value="100"></property>
<property name="acquireIncrement" value="2"></property>
</bean>
<!--掃描注解-->
<context:component-scan base-package="bb"/>
<!-- 2\. 創(chuàng)建JdbcTemplate對象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
前面搭建環(huán)境的的時候引颈,是沒有任何的事務(wù)控制的耕皮。
也就是說,當我在service中調(diào)用兩次userDao.save()蝙场,即時在中途中有異常拋出凌停,還是可以在數(shù)據(jù)庫插入一條記錄的。
- Service代碼:
@Service
public class UserService {
@Autowired
private UserDao userDao;
public void save() {
userDao.save();
int i = 1 / 0;
userDao.save();
}
}
- 測試代碼:
public class Test2 {
@Test
public void test33() {
ApplicationContext ac = new ClassPathXmlApplicationContext("bb/bean.xml");
UserService userService = (UserService) ac.getBean("userService");
userService.save();
}
}
XML方式實現(xiàn)聲明式事務(wù)控制
首先售滤,我們要配置事務(wù)的管理器類:因為JDBC和Hibernate的事務(wù)控制是不同的罚拟。
<!--1.配置事務(wù)的管理器類:JDBC-->
<bean id="txManage" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--引用數(shù)據(jù)庫連接池-->
<property name="dataSource" ref="dataSource"/>
</bean>
再而,配置事務(wù)管理器類如何管理事務(wù)
<!--2.配置如何管理事務(wù)-->
<tx:advice id="txAdvice" transaction-manager="txManage">
<!--配置事務(wù)的屬性-->
<tx:attributes>
<!--所有的方法完箩,并不是只讀-->
<tx:method name="*" read-only="false"/>
</tx:attributes>
</tx:advice>
最后赐俗,配置攔截哪些方法,
<!--3.配置攔截哪些方法+事務(wù)的屬性-->
<aop:config>
<aop:pointcut id="pt" expression="execution(* bb.UserService.*(..) )"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"></aop:advisor>
</aop:config>
配置完成之后弊知,service中的方法都應(yīng)該被Spring的聲明式事務(wù)控制了阻逮。因此我們再次測試一下:
@Test
public void test33() {
ApplicationContext ac = new ClassPathXmlApplicationContext("bb/bean.xml");
UserService userService = (UserService) ac.getBean("userService");
userService.save();
}
使用注解的方法實現(xiàn)事務(wù)控制
當然了,有的人可能覺得到XML文件上配置太多東西了秩彤。Spring也提供了使用注解的方式來實現(xiàn)對事務(wù)控制
第一步和XML的是一樣的叔扼,必須配置事務(wù)管理器類:
<!--1.配置事務(wù)的管理器類:JDBC-->
<bean id="txManage" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--引用數(shù)據(jù)庫連接池-->
<property name="dataSource" ref="dataSource"/>
</bean>
第二步:開啟以注解的方式來實現(xiàn)事務(wù)控制
<!--開啟以注解的方式實現(xiàn)事務(wù)控制-->
<tx:annotation-driven transaction-manager="txManage"/>
最后,想要控制哪個方法事務(wù)漫雷,在其前面添加@Transactional這個注解就行了瓜富!如果想要控制整個類的事務(wù),那么在類上面添加就行了珊拼。
@Transactional
public void save() {
userDao.save();
int i = 1 / 0;
userDao.save();
}
事務(wù)屬性
其實我們在XML配置管理器類如何管理事務(wù)食呻,就是在指定事務(wù)的屬性!我們來看一下事務(wù)的屬性有什么:
對于事務(wù)的隔離級別澎现,不清楚的朋友可參考我之前的博文:http://blog.csdn.net/hon_3y/article/details/53760782
事務(wù)傳播行為:
看了上面的事務(wù)屬性仅胞,沒有接觸過的其實就這么一個:propagation = Propagation.REQUIRED
事務(wù)的傳播行為。
事務(wù)傳播行為的屬性有以下這么多個剑辫,常用的就只有兩個:
- Propagation.REQUIRED【如果當前方法已經(jīng)有事務(wù)了干旧,加入當前方法事務(wù)】
- Propagation.REQUIRED_NEW【如果當前方法有事務(wù)了,當前方法事務(wù)會掛起妹蔽。始終開啟一個新的事務(wù)椎眯,直到新的事務(wù)執(zhí)行完挠将、當前方法的事務(wù)才開始】
當事務(wù)傳播行為是Propagation.REQUIRED
-
現(xiàn)在有一個日志類,它的事務(wù)傳播行為是Propagation.REQUIRED
Class Log{ Propagation.REQUIRED insertLog(); }
現(xiàn)在编整,我要在保存之前記錄日志
Propagation.REQUIRED
Void saveDept(){
insertLog();
saveDept();
}
saveDept()本身就存在著一個事務(wù)舔稀,當調(diào)用insertLog()的時候,insertLog()的事務(wù)會加入到saveDept()事務(wù)中
也就是說掌测,saveDept()方法內(nèi)始終是一個事務(wù)内贮,如果在途中出現(xiàn)了異常,那么insertLog()的數(shù)據(jù)是會被回滾的【因為在同一事務(wù)內(nèi)】
Void saveDept(){
insertLog(); // 加入當前事務(wù)
.. 異常, 會回滾
saveDept();
}
當事務(wù)傳播行為是Propagation.REQUIRED_NEW
-
現(xiàn)在有一個日志類汞斧,它的事務(wù)傳播行為是Propagation.REQUIRED_NEW
Class Log{ Propagation.REQUIRED insertLog(); }
現(xiàn)在夜郁,我要在保存之前記錄日志
Propagation.REQUIRED
Void saveDept(){
insertLog();
saveDept();
}
當執(zhí)行到saveDept()中的insertLog()方法時,insertLog()方法發(fā)現(xiàn) saveDept()已經(jīng)存在事務(wù)了粘勒,insertLog()會獨自新開一個事務(wù)竞端,直到事務(wù)關(guān)閉之后,再執(zhí)行下面的方法
如果在中途中拋出了異常庙睡,insertLog()是不會回滾的事富,因為它的事務(wù)是自己的,已經(jīng)提交了
Void saveDept(){
insertLog(); // 始終開啟事務(wù)
.. 異常, 日志不會回滾
saveDept();
}
如果文章有錯的地方歡迎指正埃撵,大家互相交流赵颅。習慣在微信看技術(shù)文章,想要獲取更多的Java資源的同學暂刘,可以關(guān)注微信公眾號:Java3y