Day17

ApacheDBUtils框架

简介

commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。因此dbutils成为很多不喜欢hibernate的公司的首选。
API介绍:
//这个就相当于昨天我们写的那个JDBCUtils,它里面提供了update和query方法,update优化增删改,query优化查找,
//所以这个QueryRunner是DBUtils最核心的类
•org.apache.commons.dbutils.QueryRunner    
//这个ResultSetHandler是一个接口,用来处理结果集的接口
•org.apache.commons.dbutils.ResultSetHandler
//下面这个工具类提供了一些简单的操作
•工具类org.apache.commons.dbutils.DbUtils
 
以上三个就是DBUtlis最核心的类
打开commons-dbutils-1.2的帮助文档可以看见
这个类就是简化JDBC增删改的
 
我们可以从它的构造方法有四种,有没有传递连接池的,也有传递连接池的,但是我们最常用的还是QueryRunner(DataSource ds) 这个,这个可以传递连接池对象
l该类简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。
lQueryRunner类提供了两类构造方法:
•默认的构造方法
•需要一个 javax.sql.DataSource 来作参数的构造方法。
 
lpublic Object query(Connection conn, String sql, Object[] params, ResultSetHandler rsh) throws SQLException:执行一个查询操作,在这个查询中,对象数组中的每个元素值被用来作为查询语句的置换参数。该方法会自行处理 PreparedStatement 和 ResultSet 的创建和关闭。
lpublic Object query(String sql, Object[] params, ResultSetHandler rsh) throws SQLException: 几乎与第一种方法一样;唯一的不同在于它不将数据库连接提供给方法,并且它是从提供给构造方法的数据源(DataSource) 或使用的setDataSource 方法中重新获得 Connection。
l
lpublic Object query(Connection conn, String sql, ResultSetHandler rsh) throws SQLException : 执行一个不需要置换参数的查询操作。
l
lpublic int update(Connection conn, String sql, Object[] params) throws SQLException:用来执行一个更新(插入、更新或删除)操作。
l
lpublic int update(Connection conn, String sql) throws SQLException:用来执行一个不需要置换参数的更新操作。
 
QueryRunner这个对象中有很多update和query方法
DBUtils的作者很人性化,面对不同的情况可以用不同的query和updata方法
例如update这个方法
update(Connection conn, String sql)     //这个是当构造函数没有传递连接池且没有参数,只有sql语句时用的
update(Connection conn, String sql, Object param)   //这个是当构造函数没有传递连接池且只有一个参数,还有sql语句时用
update(Connection conn, String sql, Object[] params)   //这个是当构造函数没有传递连接池且有很多个参数,还有sql语句时用
update(String sql)               //这个是当构造函数传递了连接池且没有参数,只有sql语句时用
update(String sql, Object param)         //这个是当构造函数传递了连接池且有一个参数,还有sql语句时用
update(String sql, Object[] params)      // 这个是当构造函数传递了连接池且有多个参数,还有sql语句时用
 
query方法也是一样有很多种,程序员可以根据需要需用调用
但是query方法中有很多都是废弃了被代替的,其实这里代替的方法好像没有意义一样,只是将参数调了一下位置
值得我们注意的是query的这个方法
query(Connection conn, String sql, Object[] params, ResultSetHandler rsh)
 
它的内部说了一句话,说的是这个方法不会关闭数据库连接,数据库连接必须由调用者去关闭
原因是这样做可以将多个sql做成一个事物,多条sql语句执行之后,由调用者关闭数据库连接
 
还有一点,对于处理结果集作者也给出了很多实现类
 
 
 
QueryRunner类不仅有对jdbc增删改查简化的方法,还有批处理的方法
 
batch(Connection conn, String sql, Object[][] params)    
//注意这里这个批处理方法传递的是二维数组Object[][] params,这是因为它是采用如下方是进行批处理的
例如sql语句是这样的:sql=insert into aa(id,name) values(?,?);
那么在批处理时,会将参数作为数组传递给数组[[1.aa],[2,bb],[3,cc]],所以这里必须传递二维数组
而这里的返回值也是int数组,表示第一个sql语句影响了几条,第二个sql语句影响了几条...
  
 
 
 
lDbUtils :提供如关闭连接、装载JDBC驱动程序等常规工作的工具类,里面的所有方法都是静态的。主要方法如下:
•public static void close(…) throws java.sql.SQLException: DbUtils类提供了三个重载的关闭方法。这些方法检查所提供的参数是不是NULL,如果不是的话,它们就关闭Connection、Statement和ResultSet。
•public static void closeQuietly(…): 这一类方法不仅能在Connection、Statement和ResultSet为NULL情况下避免关闭,还能隐藏一些在程序中抛出的SQLEeception。
•public static void commitAndCloseQuietly(Connection conn): 用来提交连接,然后关闭连接,并且在关闭连接时不抛出SQL异常。
•public static boolean loadDriver(java.lang.String driverClassName):这一方装载并注册JDBC驱动程序,如果成功就返回true。使用该方法,你不需要捕捉这个异常ClassNotFoundException。
 
 
 
下面我们就开始做练习
我们先建立好一个工程day17,然后将day16的工具包拷贝过来,然后我们采用c3p0的连接池,所以也要将c3p0-config.xml配置文件复制过来
然后将配置文件的地址改成day17,还有最重要的一点要将mysql的驱动jar包(/day17/lib/mysql-connector-java-5.0.8-bin.jar)和c3p0的jar包(/day17/lib/c3p0-0.9.2-pre1.jar和/day17/lib/mchange-commons-0.2.jar)拷过来,还有这节课要讲解DBUtils,所以我们
还要将DBUtils(/day17/lib/commons-dbutils-1.2.jar)包倒入
/day17/src/c3p0-config.xml
 
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>

    <default-config>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/day17</property>
        <property name="user">root</property>
        <property name="password">root</property>
    
        <property name="acquireIncrement">10</property>
        <property name="initialPoolSize">10</property>
        <property name="minPoolSize">5</property>
        <property name="maxPoolSize">20</property>
    </default-config><!-- This app is massive! -->
    
    
    <named-config name="mysql">
        
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/day16</property>
        <property name="user">root</property>
        <property name="password">root</property>
    
        <property name="acquireIncrement">10</property>
        <property name="initialPoolSize">10</property>
        <property name="minPoolSize">5</property>
        <property name="maxPoolSize">20</property><!-- intergalactoApp adopts a different approach to configuring statement caching -->
    </named-config>
    
    <named-config name="oracle">
        <property name="acquireIncrement">50</property>
        <property name="initialPoolSize">100</property>
        <property name="minPoolSize">50</property>
        <property name="maxPoolSize">1000</property><!-- intergalactoApp adopts a different approach to configuring statement caching -->
        <property name="maxStatements">0</property>
        <property name="maxStatementsPerConnection">5</property>
        <!-- he's important, but there's only one of him -->
        <user-overrides user="master-of-the-universe">
            <property name="acquireIncrement">1</property>
            <property name="initialPoolSize">1</property>
            <property name="minPoolSize">1</property>
            <property name="maxPoolSize">5</property>
            <property name="maxStatementsPerConnection">50</property>
        </user-overrides>
    </named-config>
</c3p0-config>

 然后我们将工具包中的配置文件修改成采用c3p0的

/day17/src/cn/itcast/utils/JdbcUtils.java

package cn.itcast.utils;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import javax.sql.DataSource;

import com.mchange.v2.c3p0.ComboPooledDataSource;

public class JdbcUtils {
    
    private static DataSource ds;
    private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
    static{
        try {
            //这里采用c3p0的连接池 ,这里没有指定用c3p0-config.xml中那个配置则默认采用缺省配置
            ds = new ComboPooledDataSource();
        } catch (Exception e) {
            throw new ExceptionInInitializerError(e);
        }
    }
    //获取连接池
    public static DataSource getDataSource(){
        return ds;
    }
    
    public static void startTransaction() throws SQLException{
        Connection conn = getConnection();
        if(conn!=null){
            conn.setAutoCommit(false);
        }
    }
    
    public static void commitTransaction() throws SQLException{
        Connection conn = getConnection();
        if(conn!=null){
            conn.commit();
        }
    }
    
    public static void rollbackTransaction() throws SQLException{
        Connection conn = getConnection();
        if(conn!=null){
            conn.rollback();
        }
    }
    
    public static void closeConnection() throws SQLException{
        Connection conn = getConnection();
        if(conn!=null){
            try{
            conn.close();
            }finally{
                tl.remove();  //关闭连接后,切记要记得移除Threadlocal中保存的连接
            }
        }
    }
    
    //加载驱动,获取链接
    public static Connection getConnection() throws SQLException{
        Connection conn = tl.get();  //得到当前线程上绑定的连接
        if(conn==null){
            conn = ds.getConnection();
            tl.set(conn);  //把得到的连接绑定到当前线程
        }
        return conn;
    }
    
    //释放资源
    public static void release(Connection conn,Statement st,ResultSet rs){
        try{
            if(rs!=null) 
                rs.close();
        }catch (Exception e) {
            e.printStackTrace();
            rs=null;
        }
        try{
            if(st!=null) st.close();
        }catch (Exception e) {
            e.printStackTrace();
            st=null;
        }
        try{
            //MyConnection
            if(conn!=null) conn.close();
        }catch (Exception e) {
            e.printStackTrace();
            conn=null;
        }
    } 
}

 

接着我们创建dao层,在创建dao层之前我们要将要操作的数据库表创建出来

create database day17 character set utf8 collate utf8_general_ci;
use day17;
create table users(
    id int primary key,
    name varchar(40),
    password varchar(40),
    email varchar(60),
    birthday date
);

 

 
然后我们开始建立dao层,来实现用DBUtils增删改查,以及获取所有数据,还有批处理
/day17/src/cn/itcast/demo/Demo1.java
package cn.itcast.demo;

import java.sql.SQLException;
import java.util.Date;
import java.util.List;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.junit.Test;

import cn.itcast.domain.User;
import cn.itcast.utils.JdbcUtils;

//使用dbutils完成数据库的crud

/*

create database day17;
use day17;

 create table users(
    id int primary key,
    name varchar(40),
    password varchar(40),
    email varchar(60),
    birthday date
);
 
 */
public class Demo1 {
    //这里测试的是增
    @Test
    public void add() throws SQLException{
        //通过DBUtils的QueryRunner方法,然后给它一个连接池
        QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "insert into users(id,name,password,email,birthday) values(?,?,?,?,?)";
        Object params[] = {1,"aaa","123","aa@sina.com",new Date()};
        //这个调用的是DBUtils中的update方法,而且是调用的是传递多个参数的update方法
        runner.update(sql, params);
    }
    
    //这里测试的是改
    @Test
    public void update() throws SQLException{
        //通过DBUtils的QueryRunner方法,然后给它一个连接池
        QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "update users set name=? where id=?";
        Object params[] = {"xxx",1};
        //这个调用的是DBUtils中的update方法,而且是调用的是传递多个参数的update方法
        runner.update(sql, params);
    }
    
    //这里测试的删除
    @Test
    public void delete() throws SQLException{
        //通过DBUtils的QueryRunner方法,然后给它一个连接池,这个是我们最常用
        QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "delete from users where id=?";
        //由于这里传递的参数只有一个,所以这里调用的传递一个参数的update方法runner.update(sql, param)
        runner.update(sql, 1);
    }
    
    //这个测试的是查找
    @Test
    public void find() throws SQLException{
        QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "select * from users where id=?";
        //这里我们调用query方法查找,这里只有一个参数,还有要传递一个结果集处理类
        //注意这里的返回值还要强转,与此同时还要建立对应的bean对象
        User user=(User) runner.query(sql, 1, new BeanHandler(User.class));
        System.out.println(user);
    }
    
    //这里是测试得到所有数据
    @Test
    public void getAll() throws SQLException{
        //不敢你要对数据库什么操作,首先new一个QueryRunner
        QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "select * from users";
        //由于获取到所有的数据,先存到bean中,然后在存入到list,最后返回list
        //所以这里传递的结果集处理类是BeanListHandler,并将对应bean类穿进去
        List list = (List) runner.query(sql, new BeanListHandler(User.class));
        System.out.println(list);
    }
    
    //这里测试批处理
    //批量向表中插入10条记录
    @Test
    public void batch() throws SQLException{
        QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "insert into users(id,name) values(?,?)";
        //要向数据库插入10条数据,这里的二维数组就必须是10
        Object params[][] = new Object[10][];
        for(int i=0;i<params.length;i++){
            //为二维数组中的每个一维数组赋值
            params[i] = new Object[]{i+1,"aa"+i};
        }
        //最后批处理执行,将sql语句和二维数组传递进去
        runner.batch(sql, params);
    }
}

在查询方法query中 DBUtils的作者写了很多结果集处理器,在学完QueryRunner这个类之后,我们很有必要去学习一下作者写的结果集

我们可以通过ResultSetHandler这个接口去查看到底作者写了多少结果集处理器

ResultSetHandler接口

l该接口用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式。
lResultSetHandler接口提供了一个单独的方法:Object handle (java.sql.ResultSet .rs)
 
ResultSetHandler 接口的实现类
lArrayHandler:把结果集中的第一行数据转成对象数组。
lArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
lBeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
lBeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
lColumnListHandler:将结果集中某一列的数据存放到List中。//这个方法比较有意思,它可以将结果集中的某一列的数据存放到list中去
lKeyedHandler(name):将结果集中的每一行数据都封装到一个Map里,再把这些map再存到一个map里,其key为指定的列名
lMapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
lMapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
最后还有一个ScalarHandler:这个结果集处理器是将结果集列转换到一个对象中,也就是说这个方法可以将结果集某一列中的数据转换到一个对象中去
下面这个图是说明KeyedHandler(name)
 /day17/src/cn/itcast/demo/Demo2.java
package cn.itcast.demo;

import java.sql.SQLException;
import java.util.List;
import java.util.Map;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.ArrayHandler;
import org.apache.commons.dbutils.handlers.ColumnListHandler;
import org.apache.commons.dbutils.handlers.KeyedHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import org.junit.Test;
import cn.itcast.utils.JdbcUtils;

//测试dbutils框架的结果集处理器
public class Demo2 {

    //测试ArrayHandler结果集处理器
    @Test
    public void testArrayHandler() throws SQLException{
        
        QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "select *from users";
        //这个ArrayHandler结果集会将结果集查到的第一行数据存到一个数组中去
        //并且这里这个ArrayHandler结果集处理器不需要传递bean类参数
        //因为它这里只会返回数据
        Object result[] = (Object[]) runner.query(sql, new ArrayHandler());
        System.out.println(result);
    }
    
    //ColumnListHandler这个结果集处理器比较有意思,它可以将结果集中的某一列
    //中的数据存入到list中去
    @Test
    public void testColumnListHandler() throws SQLException{
        QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "select *from users";
        //这里制定了将id列的数据存入到list当中
        List list = (List) runner.query(sql, new ColumnListHandler("id"));
        System.out.println(list);
    }

    //这个是测试KeyedHandler这个结果集处理器
    @Test
    public void testKeyedHandler() throws SQLException{
        QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "select *from users";
        //这里用了KeyedHandler结果集处理器,它将会把结果集中的每一行数据存入到一个独立的map中
        //最后将这些独立的map存入到一个大的map,这个大的map的key名称为指定的列名id
        //注意这里Map<Integer,Map<String,Object>>中的第二个Map中的泛型,key值为string(因为这里列名)
        //value值是任意类型的,所以这里是Object
        Map<Integer,Map<String,Object>> map = (Map) runner.query(sql, new KeyedHandler("id"));
        //然后我们对这个大的map.endtry迭代,迭代出的是一个个小的map<String,Object>
        for(Map.Entry<Integer, Map<String,Object>> entry : map.entrySet()){  //map.entryset
            Map<String,Object> innermap = entry.getValue();
            //然后迭代小的map.entry,最后将map中的键和值打印出来
            for(Map.Entry<String, Object> innerEntry : innermap.entrySet()){
                System.out.println(innerEntry.getKey() + "=" + innerEntry.getValue());
            }
        }
    }
    //这个测试ScalarHandler结果集处理器
    @Test
    public void testScalarHandler() throws SQLException{
        QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "select count(*) from users";
        //ScalarHandler这个结果集处理器可以将指定列的数据存入到一个对象中
        //这里还要注意select count(*) from users这个查询的结果是一个long类型而不是int类型
        long count = (Long) runner.query(sql, new ScalarHandler(1));  //Long
        System.out.println(count);
    }
    
    
}

 

 接下来讲解的东西是非常重要的,接下来我们要讲解在三层架构下如何处理事务管理的问题
在前面我们讲解QueryRunner时,在这个类中有很多query和update方法,这些方法都有两类
一类接受Connection,另一类没有接受Connection,但是通过连接池接受Connection,那么
接受Connection和没有接收Connection这两种方法到底有什么不同呢?不同点在于接受Connection
的方法的关闭连接必须由调用者执行,我们可以从query的方法说明中看见
如果采用这种传递Connection的方法的话,这个方法内部是不会自动关闭连接的,这个关闭连接必须由调用者去关闭
DBUtils作者之所以设计关闭连接的方法,是因为这些方法都是用于户事务管理的,如果你在这些方法中做了关闭的话
那么就不能讲多条语句作物一个事务来执行了,所以这里必须由调用者来执行关闭连接的动作
 以上这些通过传递Connection的方法都需要调用者执行关闭连接的操作,所以这些方法是用于事务处理的
 
 下面我们就通过转账来说明这个这些方法
 首先我们得建立一个数据库表
create table account(
    id int primary key auto_increment,
    name varchar(40),
    money float
)character set utf8 collate utf8_general_ci;

insert into account(name,money) values('aaa',1000);
insert into account(name,money) values('bbb',1000);
insert into account(name,money) values('ccc',1000);

 

 
 
要注意:在实际开发中dao层只能用作增删改查,不能有其它业务逻辑,如果有其它的业务逻辑就违背了三层架构的思想
下面这段代码虽然能够实现转帐,但是这样做很不优雅,在实际开发中禁止在dao层有其它的业务
/day17/src/cn/itcast/service/AccountService.java
 
package cn.itcast.demo;

import java.sql.Connection;
import java.sql.SQLException;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.junit.Test;

import cn.itcast.domain.Account;
import cn.itcast.utils.JdbcUtils;

//使用dbutils框架完成事务管理
public class AccountDao {  //dao curd

    //从a--->b转100元
    //由于在实际开发中,在dao层只能有增删改查,如果在dao层添加其它的业务逻辑就违背了
    //三层架构的思想,虽然下面这段代码可以实现转账的功能,但是这样做在开发中是不允许的
    //所以像这种事务处理在dao层是不允许的,那我们如何处理事务呢?
    //这里我们任然不违背三层架构的思想,在dao层还是写查找和更新功能,然后再service层实现
    //转账
    @Test
    public void transfer() throws SQLException{
        
        Connection conn = null;
        try{
            QueryRunner runner = new QueryRunner();
            conn = JdbcUtils.getConnection();
            conn.setAutoCommit(false);
            
            String sql1  = "update account set money=money-100 where name='aaa'";
            runner.update(conn,sql1); 
            
            //int x = 1/0;
            
            String sql2 = "update account set money=money+100 where name='bbb'";
            runner.update(conn,sql2);
            conn.commit();
        }finally{
            conn.close();
        }
    }
}

 那么我们在实际开发中该如何实现转帐呢?

在dao层我们任然定义增删改查的方法,我们可以在service层实现转帐的功能

/day17/src/cn/itcast/demo/AccountDao.java

package cn.itcast.demo;

import java.sql.Connection;
import java.sql.SQLException;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.junit.Test;

import cn.itcast.domain.Account;
import cn.itcast.utils.JdbcUtils;

//使用dbutils框架完成事务管理
public class AccountDao {  //dao curd

    //从a--->b转100元
    //由于在实际开发中,在dao层只能有增删改查,如果在dao层添加其它的业务逻辑就违背了
    //三层架构的思想,虽然下面这段代码可以实现转账的功能,但是这样做在开发中是不允许的
    //所以像这种事务处理在dao层是不允许的,那我们如何处理事务呢?
    //这里我们任然不违背三层架构的思想,在dao层还是写查找和更新功能,然后再service层实现转帐

    public Account find(int id) throws SQLException{
    //这种传递了连接池的QuryRunner会自动关闭连接Connection QueryRunner runner
= new QueryRunner(JdbcUtils.getDataSource()); String sql = "select * from account where id=?"; return (Account) runner.query(sql,id, new BeanHandler(Account.class)); } public void update(Account account) throws SQLException{ QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "update account set money=? where id=?"; Object params[] = {account.getMoney(),account.getId()}; runner.update(sql, params); } }

 /day17/src/cn/itcast/service/AccountService.java

package cn.itcast.service;

import java.sql.Connection;
import java.sql.SQLException;

import cn.itcast.demo.AccountDao;
import cn.itcast.domain.Account;
import cn.itcast.utils.JdbcUtils;

public class AccountService {

    //ThreadLocal  map set()
    //这里转帐需要将原账户的id和目标账户的id以及转多少钱传递过来
    public void transfer(int sourceid,int targetid,double money) throws SQLException{
        Connection conn = null;
        try{
            conn = JdbcUtils.getConnection();
            conn.setAutoCommit(false);
            AccountDao dao = new AccountDao(conn);
            //查找到元账户和目标账户
            Account source = dao.find(sourceid);
            Account target = dao.find(targetid);
            //将原账户和目标账户的钱更新
            source.setMoney(source.getMoney()-money);
            target.setMoney(target.getMoney()+money);
            //最后将原账户和目标账户更新到数据库中去
            dao.update(source);  //update
        //加入这里出现了异常则这个转帐程序则有安全问题,所以这种没有将原账户和目标账户放在一个事务中执行的程序是不安全的
dao.update(target); //update conn.commit(); }finally{ conn.close(); } } }

 

在实际开发里面dao层只能出现增上改查的方法,不能有其它的业务逻辑,上面这种转帐方式虽然也可以,但是还是有问题,问题出在原账户和目标账户

更新不再同一个事务中,这样会导致程序出现安全问题,所以我们下面会将上面两段代码改成在同一个事务

/day17/src/cn/itcast/demo/AccountDao.java

package cn.itcast.demo;

import java.sql.Connection;
import java.sql.SQLException;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.junit.Test;

import cn.itcast.domain.Account;
import cn.itcast.utils.JdbcUtils;

//使用dbutils框架完成事务管理
public class AccountDao {  //dao curd

    
    //这个dao通过构造饭方法接受一个Connection
    private Connection conn;
    public AccountDao(Connection conn){
        this.conn = conn;
    }
    //有一个有参数的构造方法就必须有一个无参数的构造方法
    public AccountDao() {
    }



    public Account find(int id) throws SQLException{
        //如果要用事务这里的QueryRunner就不要传递连接池,我们可以在query和update中传入连接
        //这样我们就可以手动关闭连接,从而使用事务
        QueryRunner runner = new QueryRunner();
        String sql = "select * from account where id=?";
        return (Account) runner.query(conn,sql,id, new BeanHandler(Account.class));
    }
    
    public void update(Account account) throws SQLException{
        QueryRunner runner = new QueryRunner();
        String sql = "update account set money=? where id=?";
        Object params[] = {account.getMoney(),account.getId()};
        runner.update(conn,sql, params);
    }
    
}

/day17/src/cn/itcast/service/AccountService.java

package cn.itcast.service;

import java.sql.Connection;
import java.sql.SQLException;

import cn.itcast.demo.AccountDao;
import cn.itcast.domain.Account;
import cn.itcast.utils.JdbcUtils;

public class AccountService {


    public void transfer(int sourceid,int targetid,double money) throws SQLException{
        Connection conn = null;
        try{
            //在转帐之前获取一个连接
             conn = JdbcUtils.getConnection();
            //关闭自动提交,开启事务
            conn.setAutoCommit(false);
            AccountDao dao = new AccountDao(conn);
            
            Account source = dao.find(sourceid);
            Account target = dao.find(targetid);
            
            source.setMoney(source.getMoney()-money);
            target.setMoney(target.getMoney()+money);
            
            dao.update(source);  //update
            dao.update(target);  //update
            //提交事务
            conn.commit();
        }catch(Exception e){
            e.printStackTrace();
            JdbcUtils.rollbackTransaction();
        }finally{
            //关闭连接
            conn.close();
        }
    }
}

 

以上两段代码就将转账的过程放到一个事务中去了,这里主要是想dao层传递一个Connection,而其在new QueryRunner这个对象时要用无参数的那个

然后在用它的update和find方法时将连接传递给它们

但是这样写还是不优雅,如果想优雅则会用到ThreadLocal这个类来进行事务管理,从名字来看叫局部线程,这个知识点很抽象

不好学,这个类可以想象成一个map容器,这个类有set方法向里面存数据,而关键字则是当前线程

这个ThreadLocal类相当于一个线程容器,当你向你们存数据的时候,关键字一定是当前线程

/day17/src/cn/itcast/utils/JdbcUtils.java

package cn.itcast.utils;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import javax.sql.DataSource;

import com.mchange.v2.c3p0.ComboPooledDataSource;

public class JdbcUtils {
    
    private static DataSource ds;
    //new一个ThreadLocal对象,里面存放的是Connection,这个ThreadLocal相当于一个容器
    private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
    static{
        try {
            //这里采用c3p0的连接池 ,这里没有指定用c3p0-config.xml中那个配置则默认采用缺省配置
            ds = new ComboPooledDataSource();
        } catch (Exception e) {
            throw new ExceptionInInitializerError(e);
        }
    }
    
    public static DataSource getDataSource(){
        return ds;
    }
    //关闭自动提交,开启事务
    public static void startTransaction() throws SQLException{
        //注意这个获取的连接是当前线程上的连接
        //当启动这个方法时,就获取连接开启事务,
        //还要注意这里获取连接一开始就是从连接池中获取然后绑定到当前线程上去的
        Connection conn = getConnection();
        if(conn!=null){    //如果连接不为空则开启事务
            conn.setAutoCommit(false);
        }
    }
    //提交事务
    public static void commitTransaction() throws SQLException{
        Connection conn = getConnection();//注意这个获取的连接时当前线程上的连接
        if(conn!=null){
            conn.commit();
        }
    }
    //回滚事务
    public static void rollbackTransaction() throws SQLException{
        Connection conn = getConnection();//注意这个获取的连接时当前线程上的连接
        if(conn!=null){
            conn.rollback();
        }
    }
    //关闭连接
    public static void closeConnection() throws SQLException{
        Connection conn = getConnection();        //注意这个获取的连接时当前线程上的连接
        if(conn!=null){
            try{
            conn.close();
            }finally{
                //由于这个理的ThreadLocal是一个静态的容器,所以在一个线程结束的时候一定要移除它里面的数据
                //这里一定得记得关闭呀,否则这个ThreadLocal常年类月的积累的Connection可以使内存溢出的
                tl.remove();  //关闭连接后,切记要解除当前线程上的的连接
            }
        }
    }
    
    //加载驱动,获取链接
    public static Connection getConnection() throws SQLException{
        Connection conn = tl.get();  //得到当前线程上绑定的连接
        if(conn==null){    //如果当前线程上没有绑定连接则从连接池中获取一个连接
            conn = ds.getConnection();
            tl.set(conn);  //把得到的连接绑定到当前线程
        }
        return conn;//最后将这个连接返回
    }
    
    //释放资源
    public static void release(Connection conn,Statement st,ResultSet rs){
        try{
            if(rs!=null) 
                rs.close();
        }catch (Exception e) {
            e.printStackTrace();
            rs=null;
        }
        try{
            if(st!=null) st.close();
        }catch (Exception e) {
            e.printStackTrace();
            st=null;
        }
        try{
            //MyConnection
            if(conn!=null) conn.close();
        }catch (Exception e) {
            e.printStackTrace();
            conn=null;
        }
        
    }
    
}

/day17/src/cn/itcast/demo/AccountDao.java

package cn.itcast.demo;

import java.sql.Connection;
import java.sql.SQLException;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.junit.Test;

import cn.itcast.domain.Account;
import cn.itcast.utils.JdbcUtils;

//使用dbutils框架完成事务管理
public class AccountDao {  //dao curd

    //这个dao通过构造饭方法接受一个Connection
    private Connection conn;
    public AccountDao(Connection conn){
        this.conn = conn;
    }
    //有一个有参数的构造方法就必须有一个无参数的构造方法
    public AccountDao() {
    }



    public Account find(int id) throws SQLException{
        //如果要用事务这里的QueryRunner就不要传递连接池,我们可以在query和update中传入连接
        //这样我们就可以手动关闭连接,从而使用事务
        QueryRunner runner = new QueryRunner();
        String sql = "select * from account where id=?";
        //这里的JdbcUtils.getConnection()是获取到单前线程上绑定的数据库
        return (Account) runner.query(JdbcUtils.getConnection(),sql,id, new BeanHandler(Account.class));
    }
    
    public void update(Account account) throws SQLException{
        QueryRunner runner = new QueryRunner();
        String sql = "update account set money=? where id=?";
        Object params[] = {account.getMoney(),account.getId()};
        //这里的JdbcUtils.getConnection()是获取到单前线程上绑定的数据库
        runner.update(JdbcUtils.getConnection(),sql, params);
    }
    
}

/day17/src/cn/itcast/service/AccountService.java

package cn.itcast.service;

import java.sql.Connection;
import java.sql.SQLException;

import cn.itcast.demo.AccountDao;
import cn.itcast.domain.Account;
import cn.itcast.utils.JdbcUtils;

public class AccountService {

    
    //ThreadLocal  map set()  允许用户在一个线程范围内共享数据
    public void transfer(int sourceid,int targetid,double money) throws SQLException{
        Connection conn = null;
        try{
            AccountDao dao = new AccountDao();
            //获取连接,开启事务
            //注意这里一开始获取的连接时从连接池中获取然后绑定到当前线程上去
            JdbcUtils.startTransaction();
            
            Account source = dao.find(sourceid);
            Account target = dao.find(targetid);
            
            source.setMoney(source.getMoney()-money);
            target.setMoney(target.getMoney()+money);
            
            dao.update(source);  //update
            dao.update(target);  //update
            //提交事务
            JdbcUtils.commitTransaction();
        }catch(Exception e){
            e.printStackTrace();
            //如果抛异常则回滚
            JdbcUtils.rollbackTransaction();
        }finally{
            //关闭连接
            JdbcUtils.closeConnection();
        }
    }
}

 

 通过上面的代码给我们提供了一种新的思维,那就是在一个线程范围类可以通过ThreadLocal共享数据,

在三层数据机构中如果想将某些共享数据在多个层之间共享,我们就可以在servlet里面用ThreadLocal

将这些共享数据保存起来,当一个用户访问数据库时就是一个独立的线程,所以ThreadLocal中的数据

在这个线程内是共享的

 

 事务管理的的扩展

在实际开发中我们不仅需要将多个dao层放在一个事务里面,有的时候还需要将多个service层放在同一个事务里面

 这个时候我们又该怎么做,其实在没有学sprint框架之前则会在servlet之前用了一个过滤器来拦截Connection

然后将Connection存入到TreadLocal中,然后开启线程,最后当还是通过过滤器将事务提交然后关闭连接,晴空

ThreadLocal,这种方案是在没有学sprint的时候采取的方案,其实sprint最核心的框架就是事务管理

 

 Tip:使用Jdbc操作多个表

l部门和员工(一对多关系)和(多对一关系)是一样的

 首先我们将部门和员工的对象设计出来

/day17/src/cn/itcast/domain/Department.java

 

package cn.itcast.domain;

import java.util.HashSet;
import java.util.Set;

public class Department {

    private String id;
    private String name;
    //部门有一个添加员工的集合
    private Set<Employee> employees = new HashSet();
    
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Set<Employee> getEmployees() {
        return employees;
    }
    public void setEmployees(Set<Employee> employees) {
        this.employees = employees;
    }
}

 

 /day17/src/cn/itcast/domain/Employee.java

 

package cn.itcast.domain;

public class Employee {
    
    private String id;
    private String name;
    private double salary;
    //员工有所属部门
    private Department department;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public Department getDepartment() {
        return department;
    }

    public void setDepartment(Department department) {
        this.department = department;
    }
    
}

 

 然后我们开始写一个demo3业务层,将设置一个开发部,这个开发部添加2个员工

/day17/src/cn/itcast/demo/Demo3.java

 

package cn.itcast.demo;

import java.sql.SQLException;

import org.junit.Test;

import cn.itcast.dao.DepartmentDao;
import cn.itcast.domain.Department;
import cn.itcast.domain.Employee;


//把一对多的对象存到数据库
public class Demo3 {

    @Test
    public void addDepartment() throws SQLException{
        Department d = new Department();
        d.setId("1");
        d.setName("开发部");
        
        
        Employee e1 = new Employee();
        e1.setId("1");
        e1.setName("aa");
        e1.setSalary(1000);
        
        Employee e2 = new Employee();
        e2.setId("2");
        e2.setName("bb");
        e2.setSalary(1000);
        
        d.getEmployees().add(e1);
        d.getEmployees().add(e2);
        
        
        //d
        DepartmentDao dao = new DepartmentDao();
        dao.add(d);
    }
}

 

 然后我们来做部门dao层,将部门对象存入到数据库中,但是在写dao层的同时我们先将表建立出来

 

create table department
(
    id varchar(40) primary key,
    name varchar(40)
);

create table employee
(
    id varchar(40) primary key,
    name varchar(40),
    //钱要用decimal,这里大小为10位,保留小数点2位
    salary decimal(10,2),
    //添加外键列
    department_id varchar(40),        
    //添加外键列约束
    constraint department_id_FK foreign key(department_id) references department(id)
);

alter table employee drop foreign key department_id_FK;
alter table employee add constraint department_id_FK foreign key(department_id) references department(id) on delete set null;


alter table employee drop foreign key department_id_FK;
alter table employee add constraint department_id_FK foreign key(department_id) references department(id) on delete cascade;

 

 

 

posted @ 2013-11-07 01:00  ysfox  阅读(191)  评论(0)    收藏  举报