JDBC学习笔记

时间:2016-4-13 08:00

我不是天才,只能靠坚持                                                                                                                                                                                                                   


——JDBC简介

        JDBC是Java数据库连接(Java DataBase Connectivity),由一些接口和类组成的API。
            JavaSE的一部分,由java.sql  javax.sql包组成。
            JDBC通过不同的驱动来访问数据库,这些类和方法就叫做Driver。
            不同应用程序对应不同的驱动,驱动是由数据库厂商开发的。
        JDBC为后端开发工程师解决应用问题 - 数据库之间的信息通信。
        JDBC由一组用Java语言编写的类和接口组成,适用于Java平台的后端技术。
        JDBC提供了一种基准,可以通过统一的操作访问不同的数据库。
        JDBC如何连接数据库:
                数据库服务厂商按照JDBC的基准提供相应数据的驱动。
                        将Java语句翻译成SQL语句。
                JDBC Driver Interface
                        也被称为JDBC API。
                        负责Java应用程序到对应数据库驱动之间的通信。

——JDBC API

        JDBC API由SUN公司提供,提供了Java应用程序与各种不同数据库交互的标准接口,如:Connection(连接)接口、
        Statement接口、Reparedstatement接口、ResultSet结果集。可以使用这些JDBC接口进行各类数据库的操作。
        1、DriverManager(安装驱动)
                依据数据库的不同,管理JDBC驱动。
        2、Connection(连接驱动)
                负责连接数据库并担任传送数据的任务
                URL:Uniform Resource Locator 在Internet的WWW服务程序上用于指定信息位置的表示方法
                MySql:url = "jdbc:mysql://localhost:3306/数据库名";
                SqlServer:url = "jdbc.sqlserver://localhost:1433;databasename=数据库名";
        3、Statement(发送SQL语句)
                由Connection产生,负责执行SQL语句。
        4、ResultSet(返回结果集)
                负责保存Statement执行后所产生的查询结构。

——连接数据库的步骤

        1、导入jar包。
        2、注册驱动(只做一次)
                得到MySQL的驱动(jar包),放到ClassPath中。
                所谓驱动,就是实现了JDBC接口的类。
        3、建立连接(Connection)
                底层是通过TCP/IP协议建立的socket连接。(需要开端口,占用系统资源)
        4、创建执行SQL的语句(Statement)
                有了Connection连接之后就可以发送SQL语句了。
        5、执行语句
                执行完SQL语句之后还可以返回执行结果。 
        6、处理执行结果(ResultSet)
        7、释放资源
                关闭资源的顺序与开启顺序相反:ResultSet,Statement,Connection


——注册驱动

    1、Class.forName("com.mysql.jdbc.Driver");
            推荐这种方式,不会对具体的驱动类产生依赖,也就是说,没有对应jar包,程序也能通过编译。 
            DriverManager是一个驱动管理器,可以管理很多驱动。
            其实Class.forName("类名")和DriverManager.registerDriver(Driver对象)是一样的,因为在实现的java.sql.Driver接口的Driver类的内部有一个静态代码块,会创建一个当前Driver的对象,代码如下:
                    java.sql.DriverManager.registerDriver(new Driver());

            JDBC4.0之后,每个驱动jar包中,在META-INFservices目录下都提供了一个名为java.sql.Driver的文件,文件的内容就是该接口的实现类名称。

    2、DriverManager.registerDriver(com.mysql.jdbc.Driver driver);
            会造成DriverManager中产生两个一样的驱动,并会对具体的驱动类产生依赖。
            因为指定类名会对当前环境产生依赖型。
    3、System.setProperty("jdbc.drivers","driver1:dirver2");
            虽然不会对具体的驱动类产生依赖,但是注册不太方便,所以很少使用。
            可以同时注册多个驱动,使用" : "分割。
    4、驱动类型(四种类型)


——建立连接

    1、Connection conn = DriverManager.getConnection(url,user,password);
    2、url格式
            JDBC:子协议:子名称://主机名:端口/数据库名?属性名=属性值&
            jdbc:mysql://localhost:3306/数据库名 -- mysql无子名称
            jdbc:mysql:///jdbc -- 连接的服务器是本机,端口号是默认端口号。也就是说,如果使用的是本机,可以省略主机名和端口号。
            JDBC是协议名,例如http/ftp。
    3、user、password可以用"属性名 = 属性值"的方式告诉数据库。
    4、其他参数
            useUnicode=true&characterEncoding=GBK


——释放资源

    1、释放ResultSet,Statement,Connection
    2、数据库连接(Connection)是非常稀有的资源,用完后必须马上释放,如果Connection不能及时正确的关闭将导致系统宕机,Connection使用原则是尽量晚创建,尽量早释放。

    需要注意的是,在导包时尽量使用java.sql,使用JDBC提供的接口,而不是使用com.mysql.jdbc,因为如果使用mysql包,当换成其他数据库时需要重写代码。

JDBC应用示例1
        1、电话本系统
                数据库设计:
                    1)创建数据库:myfriends
                    2)创建表:phonebook
                设计客户端:
                    创建Java项目
                            将数据库驱动添加到项目
                                    1)将驱动包添加到项目
                                    2)为了让项目识别,将驱动包添加到库。
                    创建JFrame设计。



JDBC访问数据库的步骤

public class BookFrame extends javax.swing.JFrame {
 
    private ButtonGroup group = new ButtonGroup();
 
    public BookFrame() {
        initComponents();
        group.add(rdoBoy);
        group.add(rdoGirl);
        rdoBoy.setSelected(true);//谁被选中
 
        //获取数据库集合
        ArrayList<PhoneBook> list = findAll();
 
        //将几何数据显示在表格中
        initTable(list);
    }
    public void initTable(ArrayList<PhoneBook> list)
    {
        //1获取表格模型
        DefaultTableModel dtm = (DefaultTableModel)this.tblBook.getModel();
 
        //2清空表格
        while(dtm.getRowCount() > 0)
        {
            dtm.removeRow(0);
        }
        //3显示
//        for(int i = 0; i < list.size(); i++)
//        {
//            Vector vc = new Vector();
//            vc.add(list.get(i).getPid());
//            vc.add(list.get(i).getName());
//            vc.add(list.get(i).getSex());
//            vc.add(list.get(i).getPhone());
//            vc.add(list.get(i).getAge());
//            vc.add(list.get(i).getAddress());
//            
//            dtm.addRow(vc);
//        }
        for(PhoneBook book : list)
        {
            Vector vc = new Vector();
            vc.add(book.getPid());
            vc.add(book.getName());
            vc.add(book.getSex());
            vc.add(book.getPhone());
            vc.add(book.getAge());
            vc.add(book.getAddress());
            
            dtm.addRow(vc);
            
        }
    }
    //获取数据库集合
    public ArrayList<PhoneBook> findAll()
    {
        Connection conn = null;//创建连接对象,导入java.sql包。
        Statement stmt = null;//创建执行对象
        ResultSet rs = null;//返回结果集
        ArrayList<PhoneBook> list = new ArrayList<PhoneBook>();
        //访问数据库
        try 
        {
            //访问数据库步骤
            //1、注册驱动
            //"com.mysql.jdbc.Driver"是mysql数据库驱动完整类名,必须这么写
            Class.forName("com.mysql.jdbc.Driver");
            //2、创建连接(指定连接的数据库)
            String user = "root";//数据库服务器的用户名
            String pwd = "1233";//数据库服务器的密码
            String url = "jdbc:mysql://localhost:3306/myfriends";//数据库的链接地址
            conn = DriverManager.getConnection(url, user, pwd);//应用程序与数据库驱动建立关联
            System.out.println(conn);
            //3、创建执行对象
            //执行对象是发送SQL语句给数据库驱动
            stmt = conn.createStatement();
            String sql = "select * from phonebook";
            //4、执行
            rs = stmt.executeQuery(sql);
            //5、将rs中的数据转入集合
            //rs结果集开始是指向标题的
            while(rs.next())//rs.next()返回值为布尔型,每执行一次就向下走一行
            {
                //分解rs结果集
                PhoneBook pb = new PhoneBook();
                pb.setPid(rs.getInt(1));//通过rs获取第1列的值,rs索引的开始值是1
                //也可以根据表的字段名来获取
                pb.setPid(rs.getInt("pid"));
                pb.setName(rs.getString(2));
                pb.setSex(rs.getString(3));
                pb.setPhone(rs.getString(4));
                pb.setAge(rs.getInt(5));
                pb.setAddress(rs.getString(6));
            
                //将对象添加到集合
                list.add(pb);
            }
            
        } catch (Exception e) 
        {
            
        }
        finally
        {
            try
            {
                rs.close();
                stmt.close();
                conn.close();
            }
            catch (Exception e)
            {
                
            }
        }
        return list;
    }



    //查询

        private void btQueryActionPerformed(java.awt.event.ActionEvent evt) {                                        
                //获取查询条件
                String condition = this.txtCondition.getText();
                //根据条件进行查询
                String sql = "select * from phonebook where concat(pid,name,sex,phone,age,address) like '%" +condition + "%'";
                //初始化表格
        }         



JDBC应用优化
    项目分包
            分包说明
                    util(工具类)、po(实体类)、view(窗体界面)、bl(业务逻辑类)、dao(数据访问类)
            设计顺序:
                    1)先设计公共包:util、po、view。
                    2)再设计dao包,因为po(业务逻辑)需要调用dao。
                            接口    制定业务标准和规范
                            实现类
                    3)bl设计
                            添加dao对象
                            调用dao方法
                            返回逻辑判断
                    4)完善view层
                            调用bl
                            显示数据

            表现层:VIEW
                    负责显示和输入数据
            业务逻辑层:BL/BIZ
                    需要有Po实体对象包;
                    负责传达表现层的提交业务
                    返回操作的数据
            数据访问层:DAO
                    需要用Utility包
                    专门处理数据
    执行对象Statement --> PreparedStatement


————————————————————————————


    1、优化注册驱动
            使用自定义工具类
    2、自定义工具类
            一般不允许继承,所以定义为final。
            一般不需要创建对象,所以将构造方法private。
            因为没有对象,所以使用静态。
            也可以使用单例设计模式(饿汉式或懒汉式)。
            因为驱动只需要注册一次,所以将注册驱动代码放入静态代码块。
            在注册驱动时不能直接catch,要抛出ExceptionInInitializerError(e)异常。
            将用户名和密码还有URL都放在工具类中,并私有化,方便操作。
            将创建数据库连接和关闭资源都放到工具类中,方便操作。
            释放资源需要单独进行finally操作。

            import java.sql.*;

            public class JdbcUtils {
            private static String user = "root";
            private static String password = "1233";
            private static String url = "jdbc:mysql://localhost:3306/myfriends";
            static 
            {
                try {
                    Class.forName("com.mysql.jdbc.Driver");
                }
                catch (ClassNotFoundException e) {
                    throw new ExceptionInInitializerError(e);
                }
            }
            public static Connection getConnection()
            {
                try {
                    return DriverManager.getConnection(url,user,password);
                }
                catch (SQLException e) {
                    e.printStackTrace();
                }
                return null;
            }
            public static void free(ResultSet rs,Statement st,Connection conn)
            {
                try
                {
                    if(rs != null)
                    {
                        try
                        {
                            rs.close();
                        }
                        catch(Exception e)
                        {
                            e.printStackTrace();
                        }
                        finally
                        {
                            if(st != null)
                            {
                                try
                                {
                                    st.close();
                                }
                                catch(Exception e)
                                {
                                    e.printStackTrace();
                                }
                                finally
                                {
                                    if(conn != null)
                                    {
                                        try
                                        {
                                            conn.close();
                                        }
                                        catch(Exception e)
                                        {
                                            e.printStackTrace();
                                        }
 
                                    }
                                }
                            }
                        }
                    }
                }
                catch(Exception e)
                {
                    e.printStackTrace();
                }
            }
        }


    3、增删改查
            在创建SQL语句的时候,为了增强阅读性,最好指定列名。 
            为了方便记忆,在获取字段数据的时候,最好通过字段名来获取数据,而不是第1列第2列等。
                System.out.println(rs.getObject("列名"));
            增删改用executeUpdate(sql)来完成,返回整数(修改的行数)。
            查询使用executeQuery(sql)来完成,返回的是ResultSet结果集,ResultSet中包含了查询结果,可以对ResultSet进行操作。

            import java.sql.*;

            public class CRUD {
            public static void read()
            {
                Connection conn = null;
                Statement st = null;
                ResultSet rs = null;
                try {
                    //1、建立连接
                    conn = JdbcUtils.getConnection();
                    //2、创建执行对象
                    st = conn.createStatement();
                    //3、创建SQL语句
                    String sql = "select pid,name from phonebook;";
                    //4、执行语句
                    rs = st.executeQuery(sql);
                    //5、处理结果
                    while(rs.next())
                    {
                        System.out.println("pid = " + rs.getInt("pid") + " name = " + rs.getString("name"));
                    }
                }
                catch (SQLException e)
                {
                    e.printStackTrace();
                }
                finally
                {
                    JdbcUtils.free(rs, st, conn);
                }
            }
            public static void create()
            {
                Connection conn = null;
                Statement st = null;
                ResultSet rs = null;
                try {
                    //1、建立连接
                    conn = JdbcUtils.getConnection();
                    //2、创建执行对象
                    st = conn.createStatement();
                    //3、创建SQL语句
                    String sql = "insert into phonebook(name,sex,phone,age,address) values('4','4',4,'4','4')";
                    //4、执行语句
                    //因为增删改返回的都是int型(受影响的行数),所以使用int接收返回中。
                    int result = st.executeUpdate(sql);
                    if(result > 0)
                    {
                        System.out.println("插入成功");
                        System.out.println("受影响的行:" + result);
                    }
                    else
                        System.out.println("插入失败");
 
                }
                catch (SQLException e)
                {
                    e.printStackTrace();
                }
                finally
                {
                    JdbcUtils.free(rs, st, conn);
                }
            }
            public static void update()
            {
                Connection conn = null;
                Statement st = null;
                ResultSet rs = null;
                try {
                    //1、建立连接
                    conn = JdbcUtils.getConnection();
                    //2、创建执行对象
                    st = conn.createStatement();
                    //3、创建SQL语句
                    String sql = "update phonebook set name = '张三' where pid = 2;";
                    //4、执行语句
                    //因为增删改返回的都是int型(受影响的行数),所以使用int接收返回中。
                    int result = st.executeUpdate(sql);
                    if(result > 0)
                    {
                        System.out.println("修改成功");
                        System.out.println("受影响的行:" + result);
                    }
                    else
                        System.out.println("修改失败");
                }
                catch (SQLException e)
                {
                    e.printStackTrace();
                }
                finally
                {
                    JdbcUtils.free(rs, st, conn);
                }
            }
            public static void delete()
            {
                Connection conn = null;
                Statement st = null;
                ResultSet rs = null;
                try {
                    //1、建立连接
                    conn = JdbcUtils.getConnection();
                    //2、创建执行对象
                    st = conn.createStatement();
                    //3、创建SQL语句
                    String sql = "delete from phonebook where pid = 3;";
                    //4、执行语句
                    //因为增删改返回的都是int型(受影响的行数),所以使用int接收返回中。
                    int result = st.executeUpdate(sql);
                    if(result > 0)
                    {
                        System.out.println("删除成功");
                        System.out.println("受影响的行:" + result);
                    }
                    else
                        System.out.println("删除失败");
                }
                catch (SQLException e)
                {
                    e.printStackTrace();
                }
                finally
                {
                    JdbcUtils.free(rs, st, conn);
                }
            }
        }



——DriverManager

其实今后只需要用DriverManager()的getConnection()方法即可:

JDBC4.0之后,每个驱动jar包中,在META-INFservices目录下都提供了一个名为java.sql.Driver的文件 文件的内容就是该接口的实现类名称。

1、Class.forName("com.mysql.jdbc.Driver");
2、String url = "jdbc:mysql://localhost:3306/mydb";
3、String user = "root";
4、String password ="root";
5、Connection conn = DriverManager.getConnection(url, user, password);

注意:以上代码可能出现两种异常:
    1)ClassNotFoundException:
        这个异常是在第一句中出现的,出现这个异常有两种可能:
            *   没有jar包
            *   类名打错了:com.mysql.jdbc.Driver
    2)SQLException:
        这个异常出现在第五句,出现这个异常往往是url、user或者password出错。

——Connection

Connection最为重要的方法就是获取Statement。
1、Statement stmt = conn.createStatement();

2、后面学习ResultSet时,好要学习:
    Statement stmt = conn.createStatement(int resultSetType, int resultSetConcurrency)

    conn.createStatement():
        生成的结果集:不可滚动,不敏感,不可更新。
        MySQL数据库默认可滚动。

    conn.createStatement(int , int)
        可滚动,可更新,敏感(跟随数据库变化而变化,并没有被实现)
 
    参数一:结果集类型
    可取值:
        *   ResultSet.TYPE_FORWORD_ONLY,结果集只可向前滚动。
        *   ResultSet.TYPE_SCROLL_INSENSITIVE,双向滚动,但不及时更新,就是如果数据库里的数据修改过,并不在ResultSet中反应出来。   
        *   ResultSet.TYPE_SCROLL_SENSITIVE,双向滚动,并及时跟踪数据库的更新,以便更改ResultSet中的数据。
 
    参数二:结果集可进行的操作
    可取值:
        *   ResultSet.CONCUR_READ_ONLY:结果集是只读的,不能通过修改结果集而反向影响数据库数据。
        *   ResultSet.CONCUR_UPDATABLE:结果集是可更新的,对结果集的更新可以反向影响数据库的数据。


——Statement

Statement最重要的方法是:
    *   int executeUpdate(String sql)
        执行更新操作,即执行insert、update、delete语句,其实这个方法也可以执行create table, alter table以及drop table等语句,但很少会使用JDBC来执行这些语句。

    *   ResultSet executeQuery(String sql)
        执行查询操作,该操作会返回ResultSet,即结果集。

    还有一个方法是boolean execute()方法,这个方法可以用来执行增删改查所有的SQL语句,该方法的返回值是boolean类型,表示SQL语句是否有结果集。
    如果使用execute()方法执行的是更新语句,那么还要调用int getUpdateCount()来获取insert等更新操作的受影响行数。
    如果使用execute()方法执行的是查询语句,那么还要调用ResultSet getResultSet()来获取select语句的查询结果集。


——ResultSet之滚动结果集

行与列的起始位置是1。

ResultSet表示结果集,它是一个二维的表格,ResultSet内部有一个“行光标”,ResultSet提供了一系列的方法来移动游标:

    void  beforeFirst()
        将光标放到第一行的前面,这也是光标默认的位置。

    void  afterLast()
        将光标放到最后一行的后面。

    boolean  first()
        将光标放到第一行的位置,返回值表示调控光标是否成功。

    boolean  last()
        将光标位置放到最后一行的位置。



    boolean  isBeforeFirst()
        当前光标位置是否在第一行前面。

    boolean  isAfterLast()
        当前光标位置是否在最后一行的后面。

    boolean  isFirst()
        当前光标位置是否在第一行。

    boolean  isLast()
        当前光标位置是否在最后一行。



    boolean  previous()
        光标前移一行。

    boolean  next()
        光标后移一行。

    boolean  relative(int row)
        相对位移,当row为正数时,表示向下移动row行,为负数时表示向上移动row行。

    boolean  absolute(int row)
        绝对位移,把光标移动到指定的行上。

    int  getRow()
        返回当前光标所在行。


获取结果集行数:
    得到第一行,得到最后一行,然后相减。

获取结果集列数:
    得到元数据:ResultSetMetaData  rs.getMetaData()
    获取结果集列数:int  getColumnCount()

获取指定列名称:
    得到元数据:ResultSetMetaData  rs.getMetaData()
    获取列名称:String  getColumnName(int  colIndex)


    上面的方法分为两列:一类事用来判断游标位置的,另一类是用来移动游标的,如果结果集是不可滚动的,那么只能使用next()方法来移动游标,而beforeFirst()、afterFirst()、first()、last()、previous()、relative()方法都不能使用。

    结果集是否支持滚动,要从Connection的createStatement()方法说起,也就是说,创建的Statement决定了Statement创建的ResultSet是否支持滚动。

结果集特性:
    *   是否可滚动
    *   是否敏感
    *   是否可更新


问题:如果不知道行数和列数,如何遍历结果集??

int count = rs.getMetaData().getColumnCount();
while(rs.next()){//遍历行
    for (int i = 1; i <= count; i++) //遍历列
    {
        System.out.print(rs.getObject(i));
        if(i < count)
            System.out.print(", ");
    }
    System.out.println();
}



——ResultSet之获取列数据

    可以通过next()方法使ResultSet的游标向下移动,当游标移动到所需要行时,就需要来获取该行的数据了,ResultSet提供了一系列的获取列数据的方法:

    *   String  getString(int columnIndex)
        获取指定列的String类型数据。

    *   int  getInt(int columnIndex)
        获取指定列的int类型数据。

    *   double  getDouble(int columnIndex)
        获取指定列的double类型数据

    *   boolean  getBoolean(int colimnIndex)
        获取指定列的boolean类型数据。

    *   Object  getObject(int columnIndex)
        获取指定列的Object类型的数据。

    上面方法中,参数columnIndex表示列的索引,列索引从1开始,而不是0,如果你清楚当前列的数据类型,那么可以使用getInte()之类的方法来获取,如果你不清楚列的类型,那么应该使用getObject()方法来获取。

    ResultSet还提供了一套通过列名称来获取列数据的方法:
    *   String  getString(String columnName)
        获取名为columnName的列的String类型数据。

    *   int  getInt(String columnName)
        获取名为columnName的列的int类型数据。

    *   double  getDouble(String columnName)
        获取名为columnName的列的double类型数据。

    *   boolean  getBoolean(String columnName)
        获取名为columnName的列的boolean类型数据。

    *   Object  getObject(String columnName)
        获取名为columnName的列的Object类型数据。



——PreparedStatement

     PreparedStatement是Statement的子接口。
    强大之处:
        *   防止SQL攻击
        *   提高代码的可读性、可维护性
        *   提高效率
    学习PreparedStatement的使用方法:
        *   得到PreparedStatement对象
            >   给出SQL模板
            >   调用Connection的PreparedStatement(String sql)
            >   调用pstmt的setXxx()系列方法为SQL模板中的?占位符进行赋值,下标从1开始。
            >   调用pstmt的executeUpdate()或executeQuery()方法,该方法无参数。

1、SQL注入
    PreparedStatement和Statement。
    在被执行的SQL语句中包含特殊字符或SQL的关键字(如 '1' or '1')时,Statement将出现不可预料的结果(出现异常或查询的结果不正确),可以使用PreparedStatement来解决。

        PreparedStatement(继承自Statement)相对于Statement的优点:
        1)没有SQL注入的问题。字符串的拼接会导致不可预料的结果。
        2)Statement会使数据库频繁编译SQL,可能造成数据库缓冲区溢出。
        3)数据库和驱动可以对PreparedStatement进行优化(只有在相关联的数据库连接没有关闭的情况下有效)。


        SQL注入:
            public class SQLInject {

            public static void main(String[] args)
            {
                query("'or 1 or'");
            }
            public static void query(String condition)
            {
                Connection conn = null;
                Statement st = null;
                ResultSet rs = null;
                try {
                    //1、建立连接
                    conn = JdbcUtils.getConnection();
                    //2、创建执行对象
                    st = conn.createStatement();
                    //3、创建SQL语句
                    String sql = "select pid,name from phonebook where pid = '" + condition + "';";
                    //4、执行语句
                    rs = st.executeQuery(sql);
                    //5、处理结果
                    while(rs.next())
                    {
                        System.out.println("pid = " + rs.getInt("pid") + " name = " + rs.getString("name"));
                    }
                    /*
                     * 输出结果
                        pid = 1    name = 1
                        pid = 2    name = 张三
                        pid = 4    name = 4
                        可以看出结果并不是我们想要的,所以拼接字符串的操作非常危险
                     */
                }
                catch (SQLException e)
                {
                    e.printStackTrace();
                }
                finally
                {
                    JdbcUtils.free(rs, st, conn);
                }
            }
        }



        PreparedStatement可以对SQL语句进行预处理,例如
            st.executeQuery(sql);    //在查询时传入SQL语句,并没有进行任何的处理。
            pst = conn.prepareStatement(sql);    //在创建PrepareStatement对象是传入SQL语句,进行预处理。
            因为在创建对象时已经传入了SQL语句,所以在执行
                pst.executeQuery();
            语句时就不用传入SQL语句了。

            public class SQLInject {

            public static void main(String[] args)
            {
//                query1("'or 1 or'");
                query2("张三");
            }
 
            public static void query2(String condition)
            {
                Connection conn = null;
//                Statement st = null;
                PreparedStatement pst = null;
                ResultSet rs = null;
                try {
                    //1、建立连接
                    conn = JdbcUtils.getConnection();
                    //2、创建SQL语句
                    String sql = "select pid,name from phonebook where name = ?";//?是一个占位符,用于放置对应数据
                    //3、创建执行对象
//                    st = conn.createStatement();
                    pst = conn.prepareStatement(sql);
                    pst.setString(1, condition);
                    //4、执行语句
                    rs = pst.executeQuery();
                    //5、处理结果
                    while(rs.next())
                    {
                        System.out.println("pid = " + rs.getInt("pid") + " name = " + rs.getString("name"));
                    }
                    /*
                     * 输出结果
                        pid = 2    name = 张三
                     */
                }
                catch (SQLException e)
                {
                    e.printStackTrace();
                }
                finally
                {
                    JdbcUtils.free(rs, pst, conn);
                }
            }

        }

2、防止SQL攻击
    *   过滤用户输入的数据中包含的非法字符
    *   分步校验,先使用用户名来查询用户,如果查询到了,再比较密码。

3、PreparedStatement是什么
    PreparedStatement叫预编译声明。
    PreparedStatement是Statement的子接口,可以使用PreparedStatement来替代Statement。

    PreparedStatement的优点:
        *   防止SQL攻击
        *   提高代码的可读性,以及可维护性
        *   提高效率

4、PreparedStatement的使用
    *   调用Connection的preparedStatement(String sql)方法:即创建它时就让它与一条SQL模板绑定。
    *   调用PreparedStatement的setXxx()系列方法为占位处设置值。
    *   调用executeUpdate()或executeQuery()方法执行SQL语句,需要注意的是,调用无参方法。

5、预处理的原理
    *   服务器的工作:
        >   校验SQL语句的语法
        >   编译:将SQL语句变成一个与函数相似的东西。
        >   执行:调用函数。

    *   PreparedStatement
        >   前提:连接的数据库必须支持预处理。(几乎所有数据库都支持预处理)
        >   特性:每个pstmt都与一个SQL模板绑定在一起,创建对象时会将SQL模板给数据库,数据库先进行校验,然后再进行编译,执行时只是把参数传递过去而已。
        >   第二次执行时,就不需要再次校验语法了,也不需要再次编译了。


——时间类型的转换

java.sql包中的内容绝对不能出现在Dao以外的位置。

数据库类型与Java中类型的对应关系:
    DATE —— java.sql.Date
    TIME —— java.sql.TIME 
    TIMESTAMP —— java.sql.Timestamp

    领域对象(domain包)中的所有属性不能出现java.sql包下的类型,即不能使用java.sql.Date


SQL包下时间相关的方法:
    *   java.sql.ResultSet#java.sql.Date  getDate()
    *   java.sql.PreparedStatement#setDate(int col, java.sql.Date date)

    例如user.setBirthday(rs.getDate("birthday"));,其中rs.getDate()方法返回的是java.sql.Data类型,而User的birthday是java.util.Date类型,把sql包下的Date赋给util包下的Date,这是子类对象赋给父类引用,不需要强转。

    例如pstmt.setDate(3, user,getBirthday());,其中user.getBirthday()返回的是util包下的Date,而setDate()方法参数类型是sql包下的Date,这说明需要把java.util.Date转换成java.sql.Date类型,这需要处理类型转换问题。

时间类型的转换:
    java.util.Date —— java.sql.Date、Time、Timestamp
        >   sql.Date、Time、Timestamp的构造方法接收的参数是一个毫秒值,所以可以把util.Date转换成毫秒值,然后创建sql.Date、Time、Timestamp的对象。
        >   java.util.Date date = new java.util.Date();
        >   long l = date.getTime();    //获取毫秒值
        >   java.sql.Date sqlDate = new java.sql.Date( l );
    java.sql.Date、Time、Timestamp —— java.util.Date
        >   这一步不需要进行处理了,因为java.sql.Date是java.util.Date的子类。

Java中的时间类型
    java.sql包中给出三个与数据库相关的日期时间类型,分别是:
        *   Date:表示日期,只有年月日,没有时分秒,会丢失时间。
        *   Time:表示时间,只有时分秒,没有年月日,会丢失日期。
        *   Timestamp:表示时间戳,有年月日时分秒,以及毫秒。


——Statement批处理

    批处理就是一批一批的处理数据,而不是一个一个的处理数据。
    当你有10条SQL语句要执行时,一次向服务器发送一条SQL语句,这么做效率很低,处理的方案是使用批处理,即一次性向服务器发送多条SQL语句,然后由服务器一次性处理。

    批处理只针对更新(增删改)语句,不能执行查询。

    可以多次调用Statement类的addBatch(String sql)方法,把需要执行的所有SQL语句添加到一个“批对象”中,然后调用Statement类的executeBatch()方法来执行当前“批对象”中的语句:
        *   void addBatch(String sql):添加一条语句到“批对象”中。
        *   int[] executeBatch():执行“批对象”中所有语句,返回值表示每条语句所影响的行数。
        *   void clearBatch():清空“批对象”中所有的语句。

    for (int i = 0; i < 10; i++) {
        String number = "S_10" + i;
        String name = "stu" + i;
        int age = 20 + i;
        String gender = i % 2 == 0 ? "male" : "female";
        String sql = "insert into stu values('" + number + "','" + name + "'," + age + ".'" + gender + "')";
        stmt.addBatch(sql);
    }

    stmt.executeBatch();

    当执行了“批对象”之后,“批对象”中的SQL语句就会被清空,也就是说,如果连续两次调用executeBatch()方法,相当于调用一次,因为第二次调用时,“批对象”中已经没有SQL语句了。
    还可以在执行“批对象”之前,调用Statement的clearBatch()方法来清空“批对象”中的SQL语句。

——PreparedStatement批处理

PreparedStatement的批处理有所不同,因为每个PreparedStatement对象都绑定一个SQL模板,所以向PreparedStatement中添加的不是SQL语句,而是给“?”占位符赋值。


import java.sql.Connection;
import java.sql.PreparedStatement;
 
import org.junit.Test;
 
import com.wyc.demo03.JDBCUtils;
 
/**
 * 批处理
 */
public class Demo05 {
    /**
     * pstmt对象内部有集合
     * 1、使用循环向pstmt中添加SQL参数,它有自己的SQL模板,使用一组参数与模板就可以组合成一条SQL语句
     * 2、调用它的执行批方法,向数据库发送数据
     * @throws Exception 
     */
    @Test
    public void fun() throws Exception{
        Connection conn = JDBCUtils.getConnection();
        String sql = "insert into t_stu values(?,?,?,?)";
 
        PreparedStatement pstmt = conn.prepareStatement(sql);
 
        for(int i = 0; i < 1000; i++)
        {
            pstmt.setInt(1, i);
            pstmt.setString(2, "stu_" + i);
            pstmt.setInt(3, i);
            pstmt.setString(4, i%2==0?"男":"女");
            //添加批处理对象,addBatch()方法是将这一组参数保存到集合中
            pstmt.addBatch();
        }
        long start = System.currentTimeMillis();
        //执行批处理对象
        pstmt.executeBatch();
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }
}
执行时间:43605毫秒

    打开批处理:
        MySQL的批处理也需要通过参数来打开
        在数据库链接URL后添加如下参数:
        jdbc:mysql://localhost:3306/mydb3?rewriteBatchedStatements=true
 

——JDBC事务

在JDBC中处理事务,都是通过Connection对象来完成的,同一事务中的所有操作,都必须使用同一个Connection对象来完成。

1、JDBC中的事务
    Connection的三个方法与事务相关:
        *   setAutoCommit(boolean):设置为是否自动提交事务,如果true(默认值),表示自动提交,也就是执行的每条SQL语句都是一个单独的事务,如果设置false,那么就相当于开启了事务。
        *   commit():提交事务。
        *   rollback():回滚事务。

2、演示转账

import java.sql.Connection;
import java.sql.SQLException;
 
import org.junit.Test;
 
import com.wyc.demo03.JDBCUtils;
 
/**
 * 演示转账
 * 将from账户中的金额转到to账户中
 * 所有对Connection的操作都在Service层进行处理,但是这样操作可扩展性不好
 * 可以使用配置文件的方法来处理此问题,把所有对Connection的操作隐藏起来。
 * 
 * @author 31067
 * 
 */
public class Demo01 {
    /**
     * 转账方法
     * 
     * @param from
     * @param to
     * @param balance
     */
    public void zhuanZhang(String from, String to, double balance) {
        Connection conn = null;
        try {
            // 对事务的操作必须使用同一Connection对象
            conn = JDBCUtils.getConnection();
            // 开启事务
            conn.setAutoCommit(false);
 
            AccountDao accDao = new AccountDao();
            // 给from减去相应金额
            accDao.updateBalance(from, -balance, conn);
            // 给to加上相应金额
            accDao.updateBalance(to, balance, conn);
 
            // 提交事务
            conn.commit();
            // 关闭连接
            conn.close();
        } catch (Exception e) {
            // 回滚事务
            try {
                conn.rollback();
                conn.close();
            } catch (SQLException e1) {
                throw new RuntimeException();
            }
        }
    }
}

import java.sql.Connection;
import java.sql.PreparedStatement;
 
import com.wyc.demo03.JDBCUtils;
 
public class AccountDao {
    /**
     * 修改指定用户的余额
     * 
     * @param name
     * @param balance
     */
    public void updateBalance(String name, double balance, Connection conn) {
        try {
            // 2、给出SQL模板,创建pstmt
            String sql = "update account set balance = balance + ? where name = ?";
            PreparedStatement pstmt = conn.prepareStatement(sql);
            // 对参数赋值
            pstmt.setDouble(1, balance);
            pstmt.setString(2, name);
 
            // 4、执行
            pstmt.executeUpdate();
        } catch (Exception e) {
            throw new RuntimeException();
        }
    }
}


——JDBC设置隔离级别

conn.setTransactionIsolation(int level)
参数值可选如下:
    Connection.TRANSACTION_READ_UNCOMMITTED
    Connection.TRANSACTION_READ_COMMITTED
    Connection.TRANSACTION_REPEATABLE_READ
    Connection.TRANSACTION_SERIALIZABLE

事务总结:
    *   事务的特性:ACID
    *   事务开始与结束:开始(conn.setAutoCommit(false))、结束(conn.commit())、回滚(conn.rollback())。
    *   事务的隔离级别:READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE。
当多个事务并发执行时才需要考虑并开发事务。


——JDBCUtils 1.0

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Properties;
 
 
/**
 * 2016年11月30日23:45:47
 * V1.0
 * @author 31067
 *
 */
public class JDBCUtils {
    private static Properties props = null;
    //只在JDBCUtils类被加载时执行一次
    static{
        try{
            /*
             * 1、加载配置文件
             * 2、加载驱动类
             * 3、调用DriverManager.getConnection()
             */
            //加载配置文件
            //因为src最终会进入类路径(classpath),而类加载器必然会获得类所在路径
            InputStream in = JDBCUtils.class.getClassLoader().getResourceAsStream("dbconfig.properties");
            props = new Properties();
            //获取文件内容,将文件内容放到props对象中
            props.load(in);
 
        } catch (IOException e) {
            throw new RuntimeException();
        }
 
        try{
            //加载驱动类,通过键得到值
            Class.forName(props.getProperty("driverClassName"));
        } catch (ClassNotFoundException e) {
            throw new RuntimeException();
        }
    }
 
    public static Connection getConnection() throws Exception{
        //得到Connection连接对象
        return DriverManager.getConnection(props.getProperty("url"), props.getProperty("user"), props.getProperty("password"));
    }
}



——JDBCUtils 2.0

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;
    }
}



——JDBCUtils 3.0

包含了对事务的处理。

===============================================================================

1、在Dao中处理事务

/**
 * 在Dao中处理事务
 * 但Dao中不应该存在业务,而只是对数据库的基本访问,例如增删改查操作。
 */

public class XXXService {
    public void xxx() {
        Connection conn = null;
        try {
            conn = JDBCUtils.getConnection();
            conn.setAutoCommit(false);
 
            QueryRunner qr = new QueryRunner();
            String sql1 = "...";
            Object[] params1 = {};
            qr.update(conn, sql1, params1);
 
            String sql2 = "...";
            Object[] params2 = {};
            qr.update(conn, sql2, params2);
 
            conn.commit();
        } catch (Exception e) {
            try {
                if (conn != null) {
                    conn.rollback();
                }
            } catch (Exception e2) {
                e.printStackTrace();
            }
        } finally {
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


===============================================================================

2、Service才是处理事务的地方

    我们要清楚一件事,Dao中不是处理事务的地方,因为Dao中的每个方法都是对数据库的一次操作(增删改查),而Service中的方法才是对应的业务逻辑操作(转账),也就是说我们需要在Service中的一个方法中调用Dao的多个方法,而这些方法应该在同一个事务中。

    怎么才能让Dao的多个方法使用相同的Connection呢?方法不能再自己来获得Connection,而是由外界传递进去。

    在Service中调用Dao的多个方法时,传递相同的Connection就可以了。

原代码:

import java.sql.Connection;
/**
 * 在Service中不应该出现Connection,Connection只应该在Dao中出现。
 * @author 31067
 *
 */
public class XXXService {
    // private XXXDao = new XXXDao();
    public void serviceMethod() {
        Connection conn = null;
        try {
            conn = JDBCUtils.getConnection();
            conn.setAutoCommit(false);
            dao.daoMethod1(conn, ...);
            dao.daoMethod2(conn, ...);
            dao.daoMethod3(conn, ...);
            conn.commit();
        } catch (Exception e) {
            try {
                conn.rollback();
            } catch (Exception e2) {
 
            }
        } finally {
            try {
                conn.close();
            } catch (Exception e) {
 
            }
        }
    }
}

但是,在Service中不应该出现Connection,它只应该在Dao中出现,因为Connection是JDBC的内容,JDBC是用来操作数据库的,操作数据库是Dao的工作,但是,事务是Service的工作,不能放在Dao中。 

===============================================================================

3、修改JDBCUtils

    我们把对事务的开启和关闭放到JDBCUtils中,在Service中调用JDBCUtils的方法来完成对事务的处理,这样在Service中就不会再出现Connection这一禁忌了。 


    Dao中的方法不需要再让Service来传递Connection了,Dao会主动从JDBCUtils中获取Connection链接对象,这样JDBCUtils就成为了Dao和Service 的中介了。

    我们在JDBCUtils中添加beginTransaction()、rollbaclTransaction()以及commitTransaction()方法即可,代码如下。

/**
 * 
 * @author 31067
 * 
 */
public class XXXService {
    // private XXXDao dao = new XXXDao();
    public void serviceMethod(){
        try {
            /*
             * 当开始事务时,为conn赋值,conn不再为null。
             */
            JDBCUtils.beginTransaction();
 
            /*
             * Dao方法内部会多次调用JDBCUtils.getConnection()方法,
             * 如果conn不为null,那么返回的conn是同一个Connection对象,
             * 也就是同一事务中的连接对象。
             */
            dao.daoMethod1(...);
            dao.daoMethod2(...);
            dao.daoMethod3(...);
 
            /*
             * 调用conn.commit();
             */
            JDBCUtils.commitTransaction();
        } catch (Exception e) {
            /*
             * 调用conn.rollback();
             */
            JDBCUtils.rollbackTransaction();
        }
    }
}


------------------------------------------------------------------------------------------------------------------------------

修改后的JDBCUtils

 
import java.sql.Connection;
import java.sql.SQLException;
 
import javax.sql.DataSource;
 
import com.mchange.v2.c3p0.ComboPooledDataSource;
 
public class JDBCUtils {
    // 使用的是配置文件的默认配置,即必须给出c3p0-config.xml配置文件
    private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
 
    // 事务专用连接对象
    private static Connection conn = null;
 
    /*
     * 使用连接池返回一个连接对象
     */
    public static Connection getConnection() throws SQLException {
        // 当conn不等于null时,说明已经调用过beginTransaction()方法了,表示开启了事务。
        // 然后返回当前事务的对象。 
        if (conn != null) {
            return conn;
        }
        return dataSource.getConnection();
    }
 
    /*
     * 返回连接池对象
     */
    public static DataSource getDataSource() {
        return dataSource;
    }
 
    /*
     * 开启事务
     * 1、获取一个Connection
     * 2、设置setAutoCommit(false)
     * 3、保证dao中使用的连接是自己刚刚创建的
     * ---------------------------
     * 1、创建一个Connection,设置为手动提交
     * 2、把这个Connection给dao用
     * 3、还要让commitTransaction()和rollbackTransaction()方法获取到同一个Connection对象
     */
    public static void beginTransaction() throws Exception {
        // 当已经开启事务时,调用此方法抛出异常
        if (conn == null) {
            throw new SQLException("已经开启事务!");
        }
        /*
         * 1、给con赋值,事务对象不再为null,表示开启事务
         * 2、将con设置为手动提交
         */
        conn = getConnection(); // 给conn赋值,表示开启事务
        conn.setAutoCommit(false);
    }
 
    /*
     * 提交事务
     * 1、获取beginTransaction提供的Connection
     * 2、调用commit()方法
     */
    public static void commitTransaction() throws Exception {
        // 当没有开启事务时,调用此方法抛出异常
        if (conn == null) {
            throw new SQLException("还未开启事务,不能提交!");
        }
        /*
         * 使用con.commit()方法
         */
        conn.commit();
        conn.close();
        // 把它设置为null,表示事务已经结束了,下次再去调用getConnection()返回的就不是事务专用连接了
        conn = null;
    }
 
    /*
     * 回滚事务
     * 1、获取beginTransaction提供的Connection
     * 2、调用rollback()方法
     */
    public static void rollbackTransaction() throws Exception {
        // 当没有开启事务时,调用此方法抛出异常
        if (conn == null) {
            throw new SQLException("还未开启事务,不能回滚!");
        }
        /*
         * 直接使用con.rollback()
         */
        conn.rollback();
        conn.close();
        // 把它设置为null,表示事务已经结束了,下次再去调用getConnection()返回的就不是事务专用连接了 
        conn = null;
    }

    /*
     * 释放连接
     */
    public static void releaseConnection(Connection connection) {
        /*
         * 判断是不是事务专用连接
         * 如果是事务专用,就不关闭连接
         * 如果不上事务专用,就关闭连接
         */
 
        // 如果conn == null,则说明现在没有事务在进行,那么Connection一定不是事务专用的连接对象
        if (conn == null){
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
 
        /* 
         * 如果conn != null,则说明有事务,
         * 那么需要判断参数连接是否与conn相等,
         * 如果不等,则说明参数连接不是事务专用连接。
         */
 
        if (conn != connection){
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
 

------------------------------------------------------------------------------------------------------------------------------

Dao中进行调用

import java.sql.Connection;
 
import org.apache.commons.dbutils.QueryRunner;
 
public class AccountDao {
    public static void update(String name, double balance) throws Exception {
        QueryRunner qr = new QueryRunner();
        String sql = "update account set balance = balance + ? where name = ?";
        Object[] params = { balance, name };
 
        // 我们需要自己来提供连接,保证多次调用使用的同一个连接
        Connection conn = JDBCUtils.getConnection();
 
        qr.update(conn, sql, params);

        // 关闭连接
        JDBCUtils.releaseConnection(conn); 
    }
}


如果对JDBCUtils进行了如上的改写,那么在Dao中进行连接的操作会变得非常复杂。
可以将获取连接和关闭连接封装到QueryRunner类中。

===============================================================================

4、对QueryRunner类进行装饰

import java.sql.Connection;
import java.sql.SQLException;
 
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
 
/**
 * 这个类中的方法自己来处理连接问题
 * 无需外接传递
 * 
 * 怎么处理?
 *  >  通过JDBCUtils.getConnection()得到连接,有可能是事务连接,也可能是普通的连接
 *  >  通过JDBCUtils.releaseConnection()完成对连接的释放,如果是普通连接,就关闭操作
 *  
 * 在Dao层通过QueryRunner qr = new TxQueryRunner();即可创建装饰后的类对象
 * 
 * @author 31067
 *
 */
 
public class TxQueryRunner extends QueryRunner {
 
    @Override
    public int[] batch(String sql, Object[][] params) throws SQLException {
        /*
         * 1、得到连接
         * 2、执行父类的方法,传递连接对象
         * 3、释放连接
         * 4、返回值
         * 与原方法基本一致,区别在于多了获取连接和关闭连接的操作
         */
        Connection conn = JDBCUtils.getConnection();
        int [] result = super.batch(conn, sql, params);
        JDBCUtils.releaseConnection(conn);
        return result;
    }
 
    @Override
    public <T> T query(String sql, Object param, ResultSetHandler<T> rsh) throws SQLException {
        Connection conn = JDBCUtils.getConnection();
        T result = super.query(conn, sql, param, rsh);
        JDBCUtils.releaseConnection(conn);
        return result;
    }
 
    @Override
    public <T> T query(String sql, Object[] params, ResultSetHandler<T> rsh) throws SQLException {
        Connection conn = JDBCUtils.getConnection();
        T result = super.query(conn, sql, params, rsh);
        JDBCUtils.releaseConnection(conn);
        return result;
    }
 
    @Override
    public <T> T query(String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
        Connection conn = JDBCUtils.getConnection();
        T result = super.query(conn, sql, params, rsh);
        JDBCUtils.releaseConnection(conn);
        return result;
    }
 
    @Override
    public <T> T query(String sql, ResultSetHandler<T> rsh) throws SQLException {
        Connection conn = JDBCUtils.getConnection();
        T result = super.query(conn, sql, rsh);
        JDBCUtils.releaseConnection(conn);
        return result;
    }
 
    @Override
    public int update(String sql, Object... params) throws SQLException {
        Connection conn = JDBCUtils.getConnection();
        int result = super.update(conn, sql, params);
        JDBCUtils.releaseConnection(conn);
        return result;
    }
 
    @Override
    public int update(String sql, Object param) throws SQLException {
        Connection conn = JDBCUtils.getConnection();
        int result = super.update(conn, sql, param);
        JDBCUtils.releaseConnection(conn);
        return result;
    }
 
    @Override
    public int update(String sql) throws SQLException {
        Connection conn = JDBCUtils.getConnection();
        int result = super.update(conn, sql);
        JDBCUtils.releaseConnection(conn);
        return result;
    }
}

------------------------------------------------------------------------------------------------------------------------------

对Dao层的修改:

import org.apache.commons.dbutils.QueryRunner;
import org.junit.Test;
 
public class AccountDao {
    public static void update(String name, double balance) throws Exception {
        QueryRunner qr = new TxQueryRunner();
        String sql = "update account set balance = balance + ? where name = ?";
        Object[] params = { balance, name };
 
        int result = qr.update(sql, params);
    }
}


===============================================================================

5、当前JDBCUtils还不支持事务的多线程并发访问,可以使用ThreadLocal来处理多线程的并发访问问题


import java.sql.Connection;
import java.sql.SQLException;
 
import javax.sql.DataSource;
 
import com.mchange.v2.c3p0.ComboPooledDataSource;
 
public class JDBCUtils {
    // 使用的是配置文件的默认配置,即必须给出c3p0-config.xml配置文件
    private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
 
    // 使用ThreadLocal保存事务专用连接对象
    private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
 
    /*
     * 使用连接池返回一个连接对象
     */
    public static Connection getConnection() throws SQLException {
        // 获取当前线程专属的连接
        Connection conn = tl.get();
 
        // 当conn不等于null时,说明已经调用过beginTransaction()方法了,表示开启了事务。
        if (conn != null) {
            return conn;
        }
        return dataSource.getConnection();
    }
 
    /*
     * 返回连接池对象
     */
    public static DataSource getDataSource() {
        return dataSource;
    }
 
    /*
     * 开启事务
     * 1、获取一个Connection
     * 2、设置setAutoCommit(false)
     * 3、保证dao中使用的连接是自己刚刚创建的
     * ---------------------------
     * 1、创建一个Connection,设置为手动提交
     * 2、把这个Connection给dao用
     * 3、还要让commitTransaction()和rollbackTransaction()方法获取到同一个Connection对象
     */
    public static void beginTransaction() throws Exception {
        // 获取当前线程专属的连接
        Connection conn = tl.get();
 
        // 当已经开启事务时,调用此方法抛出异常
        if (conn == null) {
            throw new SQLException("已经开启事务!");
        }
        /*
         * 1、给con赋值
         * 2、将con设置为手动提交
         */
        conn = getConnection(); // 给conn赋值,表示开启事务
        conn.setAutoCommit(false);
 
        // 在第一次获取连接时,将连接对象放入ThreadLocal中
        tl.set(conn);
    }
 
    /*
     * 提交事务
     * 1、获取beginTransaction提供的Connection
     * 2、调用commit()方法
     */
    public static void commitTransaction() throws Exception {
        // 获取当前线程专属的连接
        Connection conn = tl.get();
 
        // 当没有开启事务时,调用此方法抛出异常
        if (conn == null) {
            throw new SQLException("还未开启事务,不能提交!");
        }
        /*
         * 使用con.commit()方法
         */
        conn.commit();
        conn.close();
 
        // 从ThreadLocal中移除连接
        tl.remove();
    }
 
    /*
     * 回滚事务
     * 1、获取beginTransaction提供的Connection
     * 2、调用rollback()方法
     */
    public static void rollbackTransaction() throws Exception {
        // 获取当前线程专属的连接
        Connection conn = tl.get();
 
        // 当没有开启事务时,调用此方法抛出异常
        if (conn == null) {
            throw new SQLException("还未开启事务,不能回滚!");
        }
        /*
         * 直接使用con.rollback()
         */
        conn.rollback();
        conn.close();
 
        // 从ThreadLocal中移除连接,下次再获取时,得到的就是null了,表示重新开启事务
        tl.remove();
    }
 
    /*
     * 释放连接
     */
    public static void releaseConnection(Connection connection) {
        // 获取当前线程专属的连接
        Connection conn = tl.get();
 
        /*
         * 判断是不是事务专用连接
         * 如果是事务专用,就不关闭连接
         * 如果不上事务专用,就关闭连接
         */
 
        // 如果conn == null,则说明现在没有事务在进行,那么Connection一定不是事务专用的连接对象
        if (conn == null){
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        /* 
         * 如果conn != null,则说明有事务,
         * 那么需要判断参数连接是否与conn相等,
         * 如果不等,则说明参数连接不是事务专用连接。
         */
 
        if (conn != connection){
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
原文地址:https://www.cnblogs.com/wwwwyc/p/6375106.html