1、Service事务(DAO层可以轻易进行事务管理)
JdbcUtils初始代码:
public class JdbcUtils {
//使用配置文件的默认配置(c3p0-config.xml)
private static ComboPooledDataSource dataSource = new ComboPooledDataSource() ;
//使用连接池返回一个连接对象
public static Connection getConnection() throws SQLException {
return dataSource.getConnection() ;
}
//返回连接池对象
public static DataSource getDataSource() {
return dataSource ;
}
}
在Service中进行事务管理同时为保证Connection对象的一致性可以将Dao层的连接对象作为参数传递过来,但是,一般不会在Service层进行数据库操作。所以需要完善JdbcUtils:
private static Connection con = null;
//开启事务:获取一个Connection,设置它的setAutoCommit(false),还要保证dao中使用的连接是我们刚刚创建的
public static void beginTransaction() {
if(con != null) throw new SQLException("已经开启了事务,不要重复开启!") ;
con = getConnection() ;
con.setAutoCommit(false) ;
}
//提交事务:获取beginTransaction提供的Connection,然后调用commit方法
public static void commitTransaction() throws SQLException {
if(con == null) throw new SQLException("还未开启事务,不能提交!") ;
con.commit() ;
con.close() ;
con = null ; //因为上述close()方法只是将连接对象放回连接池,并没有真正关闭连接,所以需要手动设置连接对象为null,以保证下次连接的是新对象。
}
//回滚
public static void rollbackTransaction() {
if(con == null) throw new SQLException("还未开启事务,不能回滚!") ;
con.rollback() ;
con.close() ;
con = null ;
}
//此时DAO层连接也需要从JdbcUtils中获得,从而保证DAO层与业务层获取到的都是同一个连接
//使用连接池返回一个连接对象,通过判断可以保证在Service层调用该方法时获取到的是同一对象
public static Connection getConnection() throws SQLException {
if(con != null) return con; //此时表明调用过beginTransaction()方法开启了事务
return dataSource.getConnection() ;
}
2、JNDI配置
JNDI是Java命名和目录接口。作用:在服务器上配置资源,然后通过统一的方式来获取配置的资源。
示例:Tomcat配置连接池
<Context …>
…
<Resource name="jdbc/dataSource"
type="com.mchange.v2.c3p0.ComboPooledDataSource"
factory="org.apache.naming.factory.BeanFactory"
jdbcUrl="" driverClass="" user="" password=""
acquireIncrement="" initialPoolSize=""
/>
</Context>
配置JNDI资源需要到<Context>元素中配置<Resource>子元素
其中,name:指定资源的名称
factory:用来创建资源的工厂,值基本固定,不用修改
type:资源得类型,当前使用的是连接池的类型
bar:表示资源的属性,需要才配置。对于DBCP连接池而言需要配置的属性就是url、username等属性
>获取资源
导入jar包:(c3p0连接池:用来管理连接对象)c3p0-0.9.2-pre1.jar;mchange-commons-0.2.jar;(数据库驱动程序包)mysql-connector-Java-3.1.11-bin.jar
try{
//创建JNDI的上下文对象
Context cxt = new InitialContext();
//查询出入口
Context envContext = (Context)cxt.lookup("java:comp/env");
//二次查询,找到资源,资源名称与<Resource>元素的name对应
DataSource dataSource = (DataSource)envContext.lookup("jdbc/dataSource");
//得到连接
Connection con = dataSource.getConnection();
……
}catch(Exception e){
throw new RuntimeException(e);
}
注://查找资源时可以将两次查找合并成一句:
DataSource dataSource = (DataSource)cxt.lookup("java:comp/env/jdbc/dataSource");
3、ThreadLocal类
该类内部是Map,键为线程Thread(保存值的方法内部map.put(Thread.currentThread(),data)),值是泛型值,所以该类的最大特点是多个线程存取互不影响。
该类中的三个方法:void set(T value) 保存值;T get() 获取值;void remove() 移除值
ThreadLoca<String> t = new ThreadLoca<String>()
4、JDBC操作数据库时,除了sql语句与参数不同外,其他初始化对象部分等均相同,所以可以考虑将其他部分进行包装:
类实现:
public class QR<T> {
private DataSource dataSource ;
//外部调用构造方法时获取连接池
public QR(DataSource dataSource) {
this.dataSource = dataSource ;
}
public QR() {
super() ;
}
//外部在实例化对象(创建对象时给出连接池)后,需要给出sql语句与参数,而后调用方法执行
public int update(String sql, Object...params) {
Connection con = null ;
PreparedStatement pstmt = null ;
try {
con = dataSource.getConnection() ;
pstmt = con.PrepareStatement(sql) ;
initParams(pstmt,params) ; //调用方法传递sql语句中参数
return pstmt.executeUpdate() ;
}catch(Exception e) {
throw new RuntimeException(e) ;
}finally {
try {
if(pstmt != null) pstmt.close() ;
if(con != null) con.close() ;
}catch(SQLException e) {
e.printStackTrace() ;
}
}
}
public T query(String sql,RsHandler rh, Object...params) {
Connection con = null ;
PreparedStatement pstmt = null ;
ResultSet rs = null ;
try {
con = dataSource.getConnection() ;
pstmt = con.PrepareStatement(sql) ;
initParams(pstmt,params) ; //调用方法传递sql语句中参数
rs = pstmt.executeQuery() ;
return rh.handle(rs) ; //返回被方法处理过的结果
}catch(Exception e) {
throw new RuntimeException(e) ;
}finally {
try {
if(pstmt != null) pstmt.close() ;
if(con != null) con.close() ;
}catch(SQLException e) {
e.printStackTrace() ;
}
}
}
private void initParams(PreparedStatement pstmt,Object...params) throws Exception{
for(int i = 0; i < params.length; i++) {
pstmt.setObject(i+1, params[i]) ;
}
}
}
//用来把结果集转换成所需对象类型
interface RsHandler<T> {
public T handle(ResultSet rs) ;
}
//在类中实现RsHandle接口
RsHandler<Student> rh = new RsHandler<Student>() {
public Student handle(ResultSet rs) {
if(!rs.next()) return null ;
Student stu = new Student() ;
stu.setSid(rs.getInt("")) ;
...
return stu ;
}
}
其实,commons-dbutils-1.4.jar包中提供有解决该问题的工具类:QueryRunner类
调用方法示例:
QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource()) ;
String sql = "SELECT * FROM table WHERE id=?" ;
Object[] params = {1001} ;
//注意与上述自定义的处理方法不同点在于,执行query()方法,需要给出结果集处理器,即ResultSetHandler的实现类对象
//我们给的是BeanHandler,它实现了ResultSetHandler
//它需要一个类型,然后它会把rs中的数据封装到指定类型的javabean对象中,然后返回
Student stu = qr.query(sql, new BeanHandler<Student>(Student.class),params);
System.out.println(stu) ;
该类中的主要方法:
int update(Sstring sql , Object...params)
重载:int update(Connection con, Sstring sql , Object...params)需要调用者提供Connection,本方法不再管理Connection,即支持事务(保证了同一事务调用同一对象)
T query(String sql,ResultSetHandler rsh, Object...params)
重载:T query(Connection con,String sql,ResultSetHandler rsh, Object...params)
类中接口ResultSetHandle接口:
BeanHandler(单行):构造器需要一个Class类型参数,用来把一行结果转换为指定类型的javaBean对象
BeanListHandler(多行):构造器需要一个Class类型参数,用来把一行结果集转换为一个JavaBean,多行即为List对象
List<Student> stuList =queryrunner.query(sql, new BeanListHandler<Student>(Student.class));
MapHandler(单行):把一行结果集转换为Map对象
MapListHandler(多行):把一行记录转为一个Map,多行就是多个Map,即List<Map<String,Object>>
ScalarHandler(单行单列):返回一个Object
存在的拆箱问题:单行单列返回值得类型,若为数值型:
Number cnt = (Number)qr.query(sql,new ScalarHandler());
long c = cnt.longValue();
//查看单行单列返回值的类型
Object obj = qr.query(sql,new ScalarHandler());
输出obj.getClass().getName()
5、参照2,完善JdbcUtils:
添加释放连接方法:
public static void releaseConnection(Connection connection) throws SQLException {
/*
*判断其是否为事务专用,如果是,就不关闭。如果不是事务专用就关闭
*/
if(con == null) Connection.close() ; //此时没有事务
if(con != connection) Connection.close() ; //判断传递过来的连接对象是否是开启事务时创建的连接对象
}
6、JdbcUtils处理多线程并发问题(ThreadLocal)
//修改事务专用连接
private static ThreadLocal<Connection> t1 = new ThreadLocal<Connection>();
在执行开启事务时:
先使线程获取自身连接:Connection con = t1.get();
获取连接后(con=getConnection();)把当前线程的连接保存起来:t1.set(con);
在提交事务时:
先获取当前线程的专用连接:Connection con = t1.get();
提交事务后(con.commit();)把专用线程从t1中移除:t1.remove();