JavaWeb:JDBC之数据库连接池
JDBC系列閱讀
1. 池參數(所有池參數都有默認值)
- 初始大小:10個
- 最小空閑連接數:3個
- 增量:一次創建的最小單位(5個)
- 最大空閑連接數:12個
- 最大連接數:20個
- 最大的等待時間:1000毫秒
2. 四大連接參數
連接池也是使用四大連接參數來完成創建連接對象!
3. 實現的接口
連接池必須實現:javax.sql.DataSource接口!
連接池返回的Connection對象,它的close()方法與眾不同!調用它的close()不是關閉,而是把連接歸還給池!
4. 數據庫連接池
4.1 數據庫連接池的概念
用池來管理Connection,這可以重復使用Connection。有了池,所以我們就不用自己來創建Connection,而是通過池來獲取Connection對象。當使用完Connection后,調用Connection的close()方法也不會真的關閉Connection,而是把Connection“歸還”給池。池就可以再利用這個Connection對象了
4.2 JDBC數據庫連接池接口(DataSource)
Java為數據庫連接池提供了公共的接口:javax.sql.DataSource,各個廠商可以讓自己的連接池實現這個接口。這樣應用程序可以方便的切換不同廠商的連接池!
4.3 自定義連接池(ItcastPool)
分析:ItcastPool需要有一個List,用來保存連接對象。在ItcastPool的構造器中創建5個連接對象放到List中!當用人調用了ItcastPool的getConnection()時,那么就從List拿出一個返回。當List中沒有連接可用時,拋出異常
我們需要對Connection的close()方法進行增強,所以我們需要自定義ItcastConnection類,對Connection進行裝飾!即對close()方法進行增強。因為需要在調用close()方法時把連接“歸還”給池,所以ItcastConnection類需要擁有池對象的引用,并且池類還要提供“歸還”的方法
ItcastPool.java
public class ItcastPool implements DataSource {private static Properties props = new Properties();private List<Connection> list = new ArrayList<Connection>();static {InputStream in = ItcastPool.class.getClassLoader().getResourceAsStream("dbconfig.properties");try {props.load(in);Class.forName(props.getProperty("driverClassName"));} catch (Exception e) {throw new RuntimeException(e);}}public ItcastPool() throws SQLException {for (int i = 0; i < 5; i++) {Connection con = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"),props.getProperty("password"));ItcastConnection conWapper = new ItcastConnection(con, this);list.add(conWapper);}}public void add(Connection con) {list.add(con);}public Connection getConnection() throws SQLException {if(list.size() > 0) {return list.remove(0);}throw new SQLException("沒連接了");}...... }ItcastConnection.java
public class ItcastConnection extends ConnectionWrapper {private ItcastPool pool;public ItcastConnection(Connection con, ItcastPool pool) {super(con);this.pool = pool;}@Overridepublic void close() throws SQLException {pool.add(this);} }5. DBCP
5.1 什么是DBCP?
DBCP是Apache提供的一款開源免費的數據庫連接池!
Hibernate3.0之后不再對DBCP提供支持!因為Hibernate聲明DBCP有致命的缺欠!DBCP因為Hibernate的這一毀謗很是生氣,并且說自己沒有缺欠
5.2 DBCP的使用
public void fun1() throws SQLException {BasicDataSource ds = new BasicDataSource();ds.setUsername("root");ds.setPassword("123");ds.setUrl("jdbc:mysql://localhost:3306/mydb1");ds.setDriverClassName("com.mysql.jdbc.Driver");ds.setMaxActive(20);ds.setMaxIdle(10);ds.setInitialSize(10);ds.setMinIdle(2);ds.setMaxWait(1000);Connection con = ds.getConnection();System.out.println(con.getClass().getName());con.close(); }5.3 DBCP的配置信息
下面是對DBCP的配置介紹:
#基本配置 driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mydb1 username=root password=123#初始化池大小,即一開始池中就會有10個連接對象 默認值為0 initialSize=0#最大連接數,如果設置maxActive=50時,池中最多可以有50個連接,當然這50個連接中包含被使用的和沒被使用的(空閑) #你是一個包工頭,你一共有50個工人,但這50個工人有的當前正在工作,有的正在空閑 #默認值為8,如果設置為非正數,表示沒有限制!即無限大 maxActive=8#最大空閑連接 #當設置maxIdle=30時,你是包工頭,你允許最多有20個工人空閑,如果現在有30個空閑工人,那么要開除10個 #默認值為8,如果設置為負數,表示沒有限制!即無限大 maxIdle=8#最小空閑連接 #如果設置minIdel=5時,如果你的工人只有3個空閑,那么你需要再去招2個回來,保證有5個空閑工人 #默認值為0 minIdle=0#最大等待時間 #當設置maxWait=5000時,現在你的工作都出去工作了,又來了一個工作,需要一個工人。 #這時就要等待有工人回來,如果等待5000毫秒還沒回來,那就拋出異常 #沒有工人的原因:最多工人數為50,已經有50個工人了,不能再招了,但50人都出去工作了。 #默認值為-1,表示無限期等待,不會拋出異常。 maxWait=-1#連接屬性 #就是原來放在url后面的參數,可以使用connectionProperties來指定 #如果已經在url后面指定了,那么就不用在這里指定了。 #useServerPrepStmts=true,MySQL開啟預編譯功能 #cachePrepStmts=true,MySQL開啟緩存PreparedStatement功能, #prepStmtCacheSize=50,緩存PreparedStatement的上限 #prepStmtCacheSqlLimit=300,當SQL模板長度大于300時,就不再緩存它 connectionProperties=useUnicode=true;characterEncoding=UTF8;useServerPrepStmts=true;cachePrepStmts=true;prepStmtCacheSize=50;prepStmtCacheSqlLimit=300#連接的默認提交方式 #默認值為true defaultAutoCommit=true#連接是否為只讀連接 #Connection有一對方法:setReadOnly(boolean)和isReadOnly() #如果是只讀連接,那么你只能用這個連接來做查詢 #指定連接為只讀是為了優化!這個優化與并發事務相關! #如果兩個并發事務,對同一行記錄做增、刪、改操作,是不是一定要隔離它們啊? #如果兩個并發事務,對同一行記錄只做查詢操作,那么是不是就不用隔離它們了? #如果沒有指定這個屬性值,那么是否為只讀連接,這就由驅動自己來決定了。即Connection的實現類自己來決定! defaultReadOnly=false#指定事務的事務隔離級別 #可選值:NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE #如果沒有指定,那么由驅動中的Connection實現類自己來決定 defaultTransactionIsolation=REPEATABLE_READ6. C3P0
6.1 C3P0簡介
C3P0也是開源免費的連接池!C3P0被很多人看好!
6.2 C3P0的使用
C3P0中池類是:ComboPooledDataSource。
public void fun1() throws PropertyVetoException, SQLException {ComboPooledDataSource ds = new ComboPooledDataSource();ds.setJdbcUrl("jdbc:mysql://localhost:3306/mydb1");ds.setUser("root");ds.setPassword("123");ds.setDriverClass("com.mysql.jdbc.Driver");ds.setAcquireIncrement(5);ds.setInitialPoolSize(20);ds.setMinPoolSize(2);ds.setMaxPoolSize(50);Connection con = ds.getConnection();System.out.println(con);con.close(); }配置文件要求:
- 文件名稱:必須叫c3p0-config.xml
- 文件位置:必須在src下
c3p0也可以指定配置文件,而且配置文件可以是properties,也可騍xml的。當然xml的高級一些了。但是c3p0的配置文件名必須為c3p0-config.xml,并且必須放在類路徑下。
<?xml version="1.0" encoding="UTF-8"?> <c3p0-config><default-config><property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb1</property><property name="driverClass">com.mysql.jdbc.Driver</property><property name="user">root</property><property name="password">123</property><property name="acquireIncrement">3</property><property name="initialPoolSize">10</property><property name="minPoolSize">2</property><property name="maxPoolSize">10</property></default-config><named-config name="oracle-config"><property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb1</property><property name="driverClass">com.mysql.jdbc.Driver</property><property name="user">root</property><property name="password">123</property><property name="acquireIncrement">3</property><property name="initialPoolSize">10</property><property name="minPoolSize">2</property><property name="maxPoolSize">10</property></named-config> </c3p0-config>c3p0的配置文件中可以配置多個連接信息,可以給每個配置起個名字,這樣可以方便的通過配置名稱來切換配置信息。上面文件中默認配置為mysql的配置,名為oracle-config的配置也是mysql的配置,呵呵
public void fun2() throws PropertyVetoException, SQLException {ComboPooledDataSource ds = new ComboPooledDataSource();Connection con = ds.getConnection();System.out.println(con);con.close(); }public void fun2() throws PropertyVetoException, SQLException {ComboPooledDataSource ds = new ComboPooledDataSource("orcale-config");Connection con = ds.getConnection();System.out.println(con);con.close(); }7. Tomcat配置連接池
7.1 Tomcat配置JNDI資源
JNDI(Java Naming and Directory Interface),Java命名和目錄接口。JNDI的作用就是:在服務器上配置資源,然后通過統一的方式來獲取配置的資源
我們這里要配置的資源當然是連接池了,這樣項目中就可以通過統一的方式來獲取連接池對象了
下圖是Tomcat文檔提供的:
配置JNDI資源需要到<Context>元素中配置<Resource>子元素:
- name:指定資源的名稱,這個名稱可以隨便給,在獲取資源時需要這個名稱
- factory:用來創建資源的工廠,這個值基本上是固定的,不用修改
- type:資源的類型,我們要給出的類型當然是我們連接池的類型了
- bar:表示資源的屬性,如果資源存在名為bar的屬性,那么就配置bar的值。對于DBCP連接池而言,你需要配置的不是bar,因為它沒有bar這個屬性,而是應該去配置url、username等屬性
7.2 獲取資源
配置資源的目的當然是為了獲取資源了。只要你啟動了Tomcat,那么就可以在項目中任何類中通過JNDI獲取資源的方式來獲取資源了
下圖是Tomcat文檔提供的,與上面Tomcat文檔提供的配置資源是對應的。
獲取資源:
- Context:javax.naming.Context
- InitialContext:javax.naming.InitialContext
- lookup(String):獲取資源的方法,其中”java:comp/env”是資源的入口(這是固定的名稱),獲取過來的還是一個Context,這說明需要在獲取到的Context上進一步進行獲取。”bean/MyBeanFactory”對應<Resource>中配置的name值,這回獲取的就是資源對象了
上面兩種方式是相同的效果
7.3 修改JdbcUtils
因為已經學習了連接池,那么JdbcUtils的獲取連接對象的方法也要修改一下了。
JdbcUtils.java
public class JdbcUtils {private static DataSource dataSource = new ComboPooledDataSource();public static DataSource getDataSource() {return dataSource;}public static Connection getConnection() {try {return dataSource.getConnection();} catch (Exception e) {throw new RuntimeException(e);}} }8. ThreadLocal
Thread ->人類
Runnable -> 任務類
| thread1 | aaa |
| thread2 | bbb |
| thread3 | ccc |
8.1 ThreadLocal API
ThreadLocal類只有三個方法
| void | set(T value) | 保存值 |
| T | get() | 獲取值 |
| void | remove() | 移除值 |
8.2 ThreadLocal的內部是Map
ThreadLocal內部其實是個Map來保存數據。雖然在使用ThreadLocal時只給出了值,沒有給出鍵,其實它內部使用了當前線程做為鍵
class MyThreadLocal<T> {private Map<Thread,T> map = new HashMap<Thread,T>();public void set(T value) {map.put(Thread.currentThread(), value);}public void remove() {map.remove(Thread.currentThread());}public T get() {return map.get(Thread.currentThread());} }9. BaseServlet
9.1 BaseServlet的作用
在開始客戶管理系統之前,我們先寫一個工具類:BaseServlet
我們知道,寫一個項目可能會出現N多個Servlet,而且一般一個Servlet只有一個方法(doGet或doPost),如果項目大一些,那么Servlet的數量就會很驚人
為了避免Servlet的“膨脹”,我們寫一個BaseServlet。它的作用是讓一個Servlet可以處理多種不同的請求。不同的請求調用Servlet的不同方法。我們寫好了BaseServlet后,讓其他Servlet繼承BaseServlet,例如CustomerServlet繼承BaseServlet,然后在CustomerServlet中提供add()、update()、delete()等方法,每個方法對應不同的請求。
9.2 BaseServlet分析
我們知道,Servlet中處理請求的方法是service()方法,這說明我們需要讓service()方法去調用其他方法。例如調用add()、mod()、del()、all()等方法!具體調用哪個方法需要在請求中給出方法名稱!然后service()方法通過方法名稱來調用指定的方法
無論是點擊超鏈接,還是提交表單,請求中必須要有method參數,這個參數的值就是要請求的方法名稱,這樣BaseServlet的service()才能通過方法名稱來調用目標方法。例如某個鏈接如下:
<a href=”/xxx/CustomerServlet?method=add”>添加客戶</a>9.3 BaseServlet代碼
public class BaseServlet extends HttpServlet {/** 它會根據請求中的m,來決定調用本類的哪個方法*/protected void service(HttpServletRequest req, HttpServletResponse res)throws ServletException, IOException {req.setCharacterEncoding("UTF-8");res.setContentType("text/html;charset=utf-8");// 例如:http://localhost:8080/demo1/xxx?m=addString methodName = req.getParameter("method");// 它是一個方法名稱// 當沒用指定要調用的方法時,那么默認請求的是execute()方法。if(methodName == null || methodName.isEmpty()) {methodName = "execute";}Class c = this.getClass();try {// 通過方法名稱獲取方法的反射對象Method m = c.getMethod(methodName, HttpServletRequest.class,HttpServletResponse.class);// 反射方法目標方法,也就是說,如果methodName為add,那么就調用add方法。String result = (String) m.invoke(this, req, res);// 通過返回值完成請求轉發if(result != null && !result.isEmpty()) {req.getRequestDispatcher(result).forward(req, res);}} catch (Exception e) {throw new ServletException(e);}} }10. DBUtils
10.1 DBUtils簡介
DBUtils是Apache Commons組件中的一員,開源免費!
DBUtils是對JDBC的簡單封裝,但是它還是被很多公司使用!
DBUtils的Jar包:dbutils.jar
10.2 DBUtils主要類
- DbUtils:都是靜態方法,一系列的close()方法;
- QueryRunner:
- update():執行insert、update、delete
- query():執行select語句
- batch():執行批處理
10.3 QueryRunner
update()方法
QueryRunner的update()方法可以用來執行insert、update、delete語句。
- 無參構造QueryRunner()
還有另一種方式來使用QueryRunner,QueryRunner(DataSource)帶連接池的構造,這種方式在創建QueryRunner時傳遞了連接池對象,那么在調用update()方法時就不用再傳遞Connection了
//可執行增、刪、改語句 int update(String sql, Object… params); @Testpublic void fun2() throws SQLException {QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());String sql = "insert into user values(?,?,?)";qr.update(sql, "u1", "zhangSan", "123");}query()方法
//可執行查詢,它會先得到ResultSet,然后調用rsh的handle()把rs轉換成需要的類型! T query(String sql, ResultSetHandler rsh, Object... params);//支持事務 T query(Connection con, String sql, ResultSetHadler rsh, Object... params);10.4 ResultSetHandler
我們知道在執行select語句之后得到的是ResultSet,然后我們還需要對ResultSet進行轉換,得到最終我們想要的數據。你可以希望把ResultSet的數據放到一個List中,也可能想把數據放到一個Map中,或是一個Bean中
DBUtils提供了一個接口ResultSetHandler,它就是用來ResultSet轉換成目標類型的工具。你可以自己去實現這個接口,把ResultSet轉換成你想要的類型
DBUtils提供了很多個ResultSetHandler接口的實現,這些實現已經基本夠用了,我們通常不用自己去實現ResultSet接口了
| MapHandler | 單行處理器!把結果集轉換成Map<String,Object>,其中列名為鍵 |
| MapListHandler | 多行處理器!把結果集轉換成List<Map<String,Object>> |
| BeanHandler | 單行處理器!把結果集轉換成Bean,該處理器需要Class參數,即Bean的類型 |
| BeanListHandler | 多行處理器!把結果集轉換成List<Bean> |
| ColumnListHandler | 多行單列處理器!把結果集轉換成List<Object>,使用ColumnListHandler時需要指定某一列的名稱或編號,例如:new ColumListHandler(“name”)表示把name列的數據放到List中 |
| ScalarHandler | 單行單列處理器!把結果集轉換成Object。一般用于聚集查詢,例如select count(*) from tab_student |
Map處理器
Bean處理器
Column處理器
Scalar處理器
10.5 QueryRunner之查詢
QueryRunner的查詢方法是:
public <T> T query(String sql, ResultSetHandler<T> rh, Object… params) public <T> T query(Connection con, String sql, ResultSetHandler<T> rh, Object… params)query()方法會通過sql語句和params查詢出ResultSet,然后通過rh把ResultSet轉換成對應的類型再返回
@Test public void fun1() throws SQLException {DataSource ds = JdbcUtils.getDataSource();QueryRunner qr = new QueryRunner(ds);String sql = "select * from tab_student where number=?";Map<String,Object> map = qr.query(sql, new MapHandler(), "S_2000");System.out.println(map); }@Test public void fun2() throws SQLException {DataSource ds = JdbcUtils.getDataSource();QueryRunner qr = new QueryRunner(ds);String sql = "select * from tab_student";List<Map<String,Object>> list = qr.query(sql, new MapListHandler());for(Map<String,Object> map : list) {System.out.println(map);} }@Test public void fun3() throws SQLException {DataSource ds = JdbcUtils.getDataSource();QueryRunner qr = new QueryRunner(ds);String sql = "select * from tab_student where number=?";Student stu = qr.query(sql, new BeanHandler<Student>(Student.class), "S_2000");System.out.println(stu); }@Test public void fun4() throws SQLException {DataSource ds = JdbcUtils.getDataSource();QueryRunner qr = new QueryRunner(ds);String sql = "select * from tab_student";List<Student> list = qr.query(sql, new BeanListHandler<Student>(Student.class));for(Student stu : list) {System.out.println(stu);} }@Test public void fun5() throws SQLException {DataSource ds = JdbcUtils.getDataSource();QueryRunner qr = new QueryRunner(ds);String sql = "select * from tab_student";List<Object> list = qr.query(sql, new ColumnListHandler("name"));for(Object s : list) {System.out.println(s);} }@Test public void fun6() throws SQLException {DataSource ds = JdbcUtils.getDataSource();QueryRunner qr = new QueryRunner(ds);String sql = "select count(*) from tab_student";Number number = (Number)qr.query(sql, new ScalarHandler());int cnt = number.intValue();System.out.println(cnt); }10.6 QueryRunner之批處理
QueryRunner還提供了批處理方法:batch()
我們更新一行記錄時需要指定一個Object[]為參數,如果是批處理,那么就要指定Object[][]為參數了。即多個Object[]就是Object[][]了,其中每個Object[]對應一行記錄:
@Test public void fun10() throws SQLException {DataSource ds = JdbcUtils.getDataSource();QueryRunner qr = new QueryRunner(ds);String sql = "insert into tab_student values(?,?,?,?)";Object[][] params = new Object[10][];//表示 要插入10行記錄for(int i = 0; i < params.length; i++) {params[i] = new Object[]{"S_300" + i, "name" + i, 30 + i, i%2==0?"男":"女"};}qr.batch(sql, params); }11. Service事務
在Service中使用ThreadLocal來完成事務,為將來學習Spring事務打基礎!
11.1 DAO中的事務
在DAO中處理事務真是“小菜一碟”。
public void xxx() {Connection con = null;try {con = JdbcUtils.getConnection();con.setAutoCommitted(false);QueryRunner qr = new QueryRunner();String sql = …;Object[] params = …;qr.update(con, sql, params);sql = …;Object[] params = …;qr.update(con, sql, params);con.commit(); } catch(Exception e) {try {if(con != null) {con.rollback();} } catch(Exception e) {} } finally {try {con.close(); } catch(Exception e) {} } }11.2 Service才是處理事務的地方
我們要清楚一件事,DAO中不是處理事務的地方,因為DAO中的每個方法都是對數據庫的一次操作,而Service中的方法才是對應一個業務邏輯。也就是說我們需要在Service中的一方法中調用DAO的多個方法,而這些方法應該在一起事務中。
怎么才能讓DAO的多個方法使用相同的Connection呢?方法不能再自己來獲得Connection,而是由外界傳遞進去。
public void daoMethod1(Connection con, …) { } public void daoMethod2(Connection con, …) { }在Service中調用DAO的多個方法時,傳遞相同的Connection就可以了。
public class XXXService() {private XXXDao dao = new XXXDao();public void serviceMethod() {Connection con = null;try {con = JdbcUtils.getConnection();con.setAutoCommitted(false);dao.daoMethod1(con, …);dao.doaMethod2(con, …);com.commint();} catch(Exception e) {try {con.rollback();} catch(Exception e) {}} finally {try {con.close();} catch(Exception e) {}}} }但是,在Service中不應該出現Connection,它應該只在DAO中出現,因為它是JDBC的東西,JDBC的東西是用來連接數據庫的,連接數據庫是DAO的事兒!!!但是,事務是Service的事兒,不能放到DAO中!!!
11.3 修改JdbcUtils
我們把對事務的開啟和關閉放到JdbcUtils中,在Service中調用JdbcUtils的方法來完成事務的處理,但在Service中就不會再出現Connection這一“禁忌”了。
DAO中的方法不用再讓Service來傳遞Connection了。DAO會主動從JdbcUtils中獲取Connection對象,這樣,JdbcUtils成為了DAO和Service的中介!
我們在JdbcUtils中添加beginTransaction()和rollbackTransaction(),以及commitTransaction()方法。這樣在Service中的代碼如下:
public class XXXService() {private XXXDao dao = new XXXDao();public void serviceMethod() {try {JdbcUtils.beginTransaction();dao.daoMethod1(…);dao.daoMethod2(…);JdbcUtils.commitTransaction();} catch(Exception e) {JdbcUtils.rollbackTransaction();}} } DAO public void daoMethod1(…) {Connection con = JdbcUtils.getConnection(); } public void daoMethod2(…) {Connection con = JdbcUtils.getConnection(); }在Service中調用了JdbcUtils.beginTransaction()方法時,JdbcUtils要做準備好一個已經調用了setAuthCommitted(false)方法的Connection對象,因為在Service中調用JdbcUtils.beginTransaction()之后,馬上就會調用DAO的方法,而在DAO方法中會調用JdbcUtils.getConnection()方法。這說明JdbcUtils要在getConnection()方法中返回剛剛準備好的,已經設置了手動提交的Connection對象。
在JdbcUtils中創建一個Connection con屬性,當它為null時,說明沒有事務!當它不為null時,表示開啟了事務。
- 在沒有開啟事務時,可以調用“開啟事務”方法;
- 在開啟事務后,可以調用“提交事務”和“回滾事務”方法;
- getConnection()方法會在con不為null時返回con,再con為null時,從連接池中返回連接。
beginTransaction()
判斷con是否為null,如果不為null,就拋出異常!
如果con為null,那么從連接池中獲取一個Connection對象,賦值給con!然后設置它為“手動提交”。
getConnection()
判斷con是否為null,如果為null說明沒有事務,那么從連接池獲取一個連接返回;
如果不為null,說明已經開始了事務,那么返回con屬性返回。這說明在con不為null時,無論調用多少次getConnection()方法,返回的都是同個Connection對象。
commitTransaction()
判斷con是否為null,如果為null,說明沒有開啟事務就提交事務,那么拋出異常;
如果con不為null,那么調用con的commit()方法來提交事務;
調用con.close()方法關閉連接;
con = null,這表示事務已經結束!
rollbackTransaction()
判斷con是否為null,如果為null,說明沒有開啟事務就回滾事務,那么拋出異常;
如果con不為null,那么調用con的rollback()方法來回滾事務;
調用con.close()方法關閉連接;
con = null,這表示事務已經結束!
JdbcUtils.java
public class JdbcUtils {private static DataSource dataSource = new ComboPooledDataSource();private static Connection con = null;public static DataSource getDataSource() {return dataSource;}public static Connection getConnection() throws SQLException {if(con == null) {return dataSource.getConnection();}return con;}public static void beginTranscation() throws SQLException {if(con != null) {throw new SQLException("事務已經開啟,在沒有結束當前事務時,不能再開啟事務!");}con = dataSource.getConnection();con.setAutoCommit(false);}public static void commitTransaction() throws SQLException {if(con == null) {throw new SQLException("當前沒有事務,所以不能提交事務!");}con.commit();con.close();con = null;}public static void rollbackTransaction() throws SQLException {if(con == null) {throw new SQLException("當前沒有事務,所以不能回滾事務!");}con.rollback();con.close();con = null; } }11.4 再次修改JdbcUtils
現在JdbcUtils有個問題,如果有兩個線程!第一個線程調用了beginTransaction()方法,另一個線程再調用beginTransaction()方法時,因為con已經不再為null,所以就會拋出異常了。
我們希望JdbcUtils可以多線程環境下被使用!這說明最好的方法是為每個線程提供一個Connection,這樣每個線程都可以開啟自己的事務了。
還記得ThreadLocal類么?
11.5 轉賬示例
public class AccountDao {public void updateBalance(String name, double balance) throws SQLException {String sql = "update account set balance=balance+? where name=?";Connection con = JdbcUtils.getConnection();QueryRunner qr = new QueryRunner();qr.update(con, sql, balance, name);} } public class AccountService {private AccountDao dao = new AccountDao();public void transfer(String from, String to, double balance) {try {JdbcUtils.beginTranscation();dao.updateBalance(from, -balance);dao.updateBalance(to, balance);JdbcUtils.commitTransaction();} catch(Exception e) {try {JdbcUtils.rollbackTransaction();} catch (SQLException e1) {throw new RuntimeException(e);}}} } AccountService as = new AccountService(); as.transfer("zs", "ls", 100); 《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的JavaWeb:JDBC之数据库连接池的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JavaWeb开发概述
- 下一篇: JavaWeb:JDBC之事务