JDBC、数据库事务、DBUtils(许多东西在代码的注释中,耐心读)

1.数据库事务

1.1 基础概念

1.1.1 什么叫做数据库事务

  • 事务:就是指一组逻辑单元从一种状态,到另外一种状态。
  • 一种逻辑单元:一个或者多个DML操作

1.1.2 事务处理的原则

保证所有的事物都作为一个工作单元来执行,即使出现了故障都不能改变这种工作方式,当在一个事务中执行多个操作的时候,要么所有的事物都被提交,那么修改永久的保存了下来,要么放弃所作的所有修改,整个事务回滚到最初的状态。
事务一旦提交就不可回滚

1.1.3 哪些操作会导致数据的提交

  • DDL操作一旦执行,都会自动提交(CREATE TABLE/VIEW/INDEX/SYN/CLUSTER)set autocommit=false对DDL操作失效
  • DML默认情况下,一旦执行,就会自动提交(INSERT/UPDATE/DELETE)我们可以通过set autocommit=false的方式取消DML操作的自动提交

默认在关闭连接时会自动的提交

1.2 事务的ACID属性

  • A(Atomicity) 原子性,原子性是指事务是一个不可分割的工作单位,事务的操作要么都发生,要么都不发生。
  • C(Consistency)一致性,事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
  • I(Isolation)隔离性,(分了四种隔离级别)指的是一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰
  • D(Durability)持久性,一个事务一旦提交,对数据库的事务改变就是永久性的接下来的操作及数据库故障不应该对其产生任何影响。

1.3 数据库并发的三种问题

  • 脏读:对于两个事务,T1和T2,T1读取了T2更新但未提交的字段,之后T2回滚,T1读到的数据就是临时且无效的了。成为脏读
  • 不可重复度:对于两个事务,T1和T2,T1读取了一个字段的值,之后T2修改了该字段,T1再次读取该字段时发现值和上一次不同。
  • 幻读:类似于不可重复读,T1读取一张表,之后T2插入新数据,T1再次读取这张表时发现多了几条新数据。

1.4 四种隔离级别

  • READ UNCOMMITED 读未提交,不解决任何并发问题
  • READ COMMIT 读已提交,解决了脏读问题
  • REPEATABLE READ 可重复读
  • SERIALIZABLE 串行化,效率十分低下

1.5 事务传播行为

摘自这里

1.5.1 前言

Spring在Spring在TransactionDefinition接口中规定了7种类型的事务传播行为。事务传播行为是Spring框架独有的事务增强特性,他不属于的事务实际提供方数据库行为。这是Spring为我们提供的强大的工具箱,使用事务传播行可以为我们的开发工作提供许多便利。但是人们对他的误解也颇多,你一定也听过“service方法事务最好不要嵌套”的传言。要想正确的使用工具首先需要了解工具。本文对七种事务传播行为做详细介绍,内容主要代码示例的方式呈现。

1.5.2 什么是事务传播行为

事务传播行为用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的时事务如何传播。
代码说明:

public void methodA(){
    methodB();
    //doSomething
 }
 
 @Transaction(Propagation=XXX)
 public void methodB(){
    //doSomething
 }

代码中methodA()方法嵌套调用了methodB()方法,methodB()的事务传播行为由@Transaction(Propagation=XXX)设置决定。这里需要注意的是methodA()并没有开启事务,某一个事务传播行为修饰的方法并不是必须要在开启事务的外围方法中调用。

1.5.3 Spring中七种事务传播行为

事务传播行为类型说明
PROPAGATION_REQUIRED如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
PROPAGATION_SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

1.5.4 使用代码对上述的传播行为验证

使用mysql数据库,创建两张表:

CREATE TABLE `user1` (
  `id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45) NOT NULL DEFAULT '',
  PRIMARY KEY(`id`)
)
ENGINE = InnoDB;
CREATE TABLE `user2` (
  `id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45) NOT NULL DEFAULT '',
  PRIMARY KEY(`id`)
)
ENGINE = InnoDB;

编写相应的Bean和DAO层代码

User1

public class User1 {
    private Integer id;
    private String name;
   //get和set方法省略...
}

User2

public class User2 {
    private Integer id;
    private String name;
   //get和set方法省略...
}

User1Mapper

public interface User1Mapper {
    int insert(User1 record);
    User1 selectByPrimaryKey(Integer id);
    //其他方法省略...
}

User2Mapper

public interface User2Mapper {
    int insert(User2 record);
    User2 selectByPrimaryKey(Integer id);
    //其他方法省略...
}

验证代码由service层实现

  • PROPAGATION_REQUIRED

我们为User1Service和User2Service相应方法加上Propagation.REQUIRED属性。
UserService1方法

@Service
public class User1ServiceImpl implements User1Service {
    //省略其他...
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void addRequired(User1 user){
        user1Mapper.insert(user);
    }
}

UserService2方法

@Service
public class User2ServiceImpl implements User2Service {
    //省略其他...
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void addRequired(User2 user){
        user2Mapper.insert(user);
    }
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void addRequiredException(User2 user){
        user2Mapper.insert(user);
        throw new RuntimeException();
    }
    
}

场景一:外围方法不开启事务
验证方法一:

@Override
    public void notransaction_exception_required_required(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequired(user1);
        
        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addRequired(user2);
        
        throw new RuntimeException();
    }

验证方法二:

    @Override
    public void notransaction_required_required_exception(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequired(user1);
        
        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addRequiredException(user2);
    }

验证方法一的结果:张三李四均插入,外围方法未开启事务,两个方法开启自己独立的事务且不互相干扰,外围方法抛出异常不影响自己的事务。

验证方法二结果:张三插入,李四未插入。张三和李四都在自己的事务中执行,李四事务抛出异常则李四事务回滚,并不影响张三。

结论:在外围方法未开启事务的情况下Propagation.REQUIRED修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。

场景二:外围方法开启事务,使用率比较高

验证方法一:

@Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void transaction_exception_required_required(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequired(user1);
        
        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addRequired(user2);
        
        throw new RuntimeException();
    }

验证方法二:

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void transaction_required_required_exception(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequired(user1);
        
        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addRequiredException(user2);
    }

验证方法三:

    @Transactional
    @Override
    public void transaction_required_required_exception_try(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequired(user1);
        
        User2 user2=new User2();
        user2.setName("李四");
        try {
            user2Service.addRequiredException(user2);
        } catch (Exception e) {
            System.out.println("方法回滚");
        }
    }

验证方法一的结果:张三李四均未插入,外围方法开启了自己的事务,张三李四加入外围事务,外围方法抛出异常,事务回滚,张三李四也回滚。

验证方法二的结果:张三李四均未插入,李四方法抛出异常,外围方法也抛异常,和上面差不多,所以张三李四均未插入。

验证方法三的结果:张三李四均未插入,张三李四加入到外围方法的事务,内部方法抛出异常回滚,即使方法被catch不被外围方法感知,整个事务依然回滚(因为内部事务和外围是同一个事务)。

结论:在外围方法开启事务的情况下Propagation.REQUIRED修饰的内部方法会加入到外围方法的事务中,所有Propagation.REQUIRED修饰的内部方法和外围方法均属于同一事务,只要一个方法回滚,整个事务均回滚。

  • PROPAGATION_REQUIRES_NEW

我们为User1Service和User2Service相应方法加上Propagation.REQUIRES_NEW属性。

User1Service方法:

@Service
public class User1ServiceImpl implements User1Service {
    //省略其他...
    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void addRequiresNew(User1 user){
        user1Mapper.insert(user);
    }
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void addRequired(User1 user){
        user1Mapper.insert(user);
    }
}

User2Service方法:

@Service
public class User2ServiceImpl implements User2Service {
    //省略其他...
    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void addRequiresNew(User2 user){
        user2Mapper.insert(user);
    }
    
    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void addRequiresNewException(User2 user){
        user2Mapper.insert(user);
        throw new RuntimeException();
    }
}

场景一:外围方法没开启事务

验证方法一:

@Override
    public void notransaction_exception_requiresNew_requiresNew(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequiresNew(user1);
        
        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addRequiresNew(user2);
        throw new RuntimeException();
        
    }

验证方法二:

 @Override
    public void notransaction_requiresNew_requiresNew_exception(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequiresNew(user1);
        
        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addRequiresNewException(user2);
    }

验证方法一结果:张三李四均插入,外围方法没开启事务,都在自己独立的事务中进行,外围方法的异常不影响内部事务。

验证方法二结果:张三插入,李四未插入,李四抛出异常李四回滚,外围方法没有事务,感知到异常也不影响张三的插入。

结论:在外围方法未开启事务的情况下Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
场景二:外围方法开启事务

验证方法一:

 @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void transaction_exception_required_requiresNew_requiresNew(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequired(user1);
        
        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addRequiresNew(user2);
        
        User2 user3=new User2();
        user3.setName("王五");
        user2Service.addRequiresNew(user3);
        throw new RuntimeException();
    }

验证方法二:

  @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void transaction_required_requiresNew_requiresNew_exception(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequired(user1);
        
        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addRequiresNew(user2);
        
        User2 user3=new User2();
        user3.setName("王五");
        user2Service.addRequiresNewException(user3);
    }

验证方法三:

 @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void transaction_required_requiresNew_requiresNew_exception_try(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequired(user1);
        
        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addRequiresNew(user2);
        User2 user3=new User2();
        user3.setName("王五");
        try {
            user2Service.addRequiresNewException(user3);
        } catch (Exception e) {
            System.out.println("回滚");
        }
    }

验证方法一的结果:张三未插入,李四和王五均插入,张三的方法使用的是addRequired,和外围方法属于同一事务,李四和王五都是使用的Requires_new,会创建属于自己的独立的事务,所以外围方法抛出异常,影响外围事务回滚,李四和王五不受影响插入。

验证方法二的结果:张三王五未插入,李四插入,张三和外围方法属于同一个事物,王五独立事务抛出异常,王五回滚,外围方法感知王五抛出的异常,外围方法事务回滚,李四独立事务不受影响。

验证方法三:张三插入、李四插入、王五未插入,王五独立事务抛出异常,王五回滚,张三与外部方法属于同一事物,异常被捕获未被感知到,不回滚,李四不受他俩影响(因为内部抛异常事务不属于外围事务)。

结论:在外围方法开启事务的情况下Propagation.REQUIRES_NEW修饰的内部方法依然会单独开启独立事务,且与外部方法事务也独立,内部方法之间、内部方法和外部方法事务均相互独立,互不干扰。

  • PROPAGATION_NESTED

我们为User1Service和User2Service相应方法加上Propagation.NESTED属性。

User1Service

@Service
public class User1ServiceImpl implements User1Service {
    //省略其他...
    @Override
    @Transactional(propagation = Propagation.NESTED)
    public void addNested(User1 user){
        user1Mapper.insert(user);
    }
}

User2Service方法:

@Service
public class User2ServiceImpl implements User2Service {
    //省略其他...
    @Override
    @Transactional(propagation = Propagation.NESTED)
    public void addNested(User2 user){
        user2Mapper.insert(user);
    }
    
    @Override
    @Transactional(propagation = Propagation.NESTED)
    public void addNestedException(User2 user){
        user2Mapper.insert(user);
        throw new RuntimeException();
    }
}

场景一:外围方法没有开启事务

验证方法一:

@Override
    public void notransaction_exception_nested_nested(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addNested(user1);
        
        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addNested(user2);
        throw new RuntimeException();
    }

验证方法二:

  @Override
    public void notransaction_nested_nested_exception(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addNested(user1);
        
        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addNestedException(user2);
    }

验证方法一的结果:张三李四均插入,外围方法不影响张三李四的独立事务

验证方法二的结果:张三插入,李四未插入,张三在自己独立事务中执行,李四抛异常李四回滚,张三不受影响。

结论:在外围方法未开启事务的情况下Propagation.NESTED和Propagation.REQUIRED作用相同,修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰。

场景二:外围方法开启事务

验证方法一:

 @Transactional
    @Override
    public void transaction_exception_nested_nested(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addNested(user1);
        
        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addNested(user2);
        throw new RuntimeException();
    }

验证方法二:

 @Transactional
    @Override
    public void transaction_nested_nested_exception(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addNested(user1);
        
        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addNestedException(user2);
    }

验证方法三:

   @Transactional
    @Override
    public void transaction_nested_nested_exception_try(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addNested(user1);
        
        User2 user2=new User2();
        user2.setName("李四");
        try {
            user2Service.addNestedException(user2);
        } catch (Exception e) {
            System.out.println("方法回滚");
        }
    }

验证方法一的结果:张三李四均未插入外围方法开启事务,内部事务为外部事务的子事务,外部事务回滚,内部事务也要回滚。

验证方法二结果:张三李四均为插入,内部事务抛出异常回滚,且外围方法感知异常,致使整体回滚。

验证方法三结果:张三插入、李四未插入,李四抛出异常回滚,外围事务可以单独对子事务单独回滚。内部子事务单独回滚。

结论:在外围方法开启事务的情况下Propagation.NESTED修饰的内部方法属于外部事务的子事务,外围主事务回滚,子事务一定回滚,而内部子事务可以单独回滚而不影响外围主事务和其他子事务。NESTED没有将事务融合,而是作为子事务出现,而REQUIRED是将事务融合在一起。

  • REQUIRED,REQUIRES_NEW,NESTED异同。

外围方法不开启事务时:

  • 三者会新建自己的事务,且事务之间相互独立

外围方法开启事务时:

  • REQUIRED,会将当前事务加入到外围事务中,只要外围事务回滚就全部回滚。
  • REQUIRES_NEW,无论外围是否开启事务都会创建自己的事务并独立于外围事务。
  • NESTED,会创建自己的事务,并以子事务的形式加入到外围事务中,回滚时可以只回滚某个子事务。如果外围事务整体回滚,则子事务们也受影响。

其他的自己测试把

2.JDBC基础操作

2.1 获取连接的几种方法

因为使用的时MySQL数据库,所以要导入mysql的驱动包:
pom坐标如下:

<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.20</version>
</dependency>

获取连接的信息可以写在配置文件中:
jdbc.properties

url=jdbc:mysql://localhost:3306/jdbc_study?serverTimezone=GMT&rewriteBatchedStatements=true
driver=com.mysql.cj.jdbc.Driver
user=root
password=root

获取连接的代码:

package com;

import org.junit.Test;

import javax.swing.*;
import java.io.IOException;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

public class ConnectionTest {
    /**
     * 方式一,获取连接
     * @throws SQLException
     */
    @Test
    public void testCon1() throws SQLException {
        Driver driver=new com.mysql.cj.jdbc.Driver();
        Properties properties=new Properties();
        String url="jdbc:mysql://localhost:3306/jdbc_study?serverTimezone=GMT";
        Connection connect=null;
        try {
            properties.setProperty("user","root");
            properties.setProperty("password","root");
            System.out.println(properties.getProperty("user"));
            connect = driver.connect(url, properties);
            System.out.println(connect);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            if (connect!=null){
                connect.close();
            }
        }
    }

    /**
     * 方式二:获取连接
     * 目的:在程序中不出现第三方的api,使程序具有更好的可移植性
     */
    @Test
    public void textCon2() throws Exception {
        //1.获取driver类对象
        Class<?> aClass = Class.forName("com.mysql.cj.jdbc.Driver");
        Driver driver= (Driver) aClass.newInstance();
        //2.提供要连接的数据库
        String url="jdbc:mysql://localhost:3306/jdbc_study?serverTimezone=GMT";
        Properties properties=new Properties();
        properties.setProperty("user","root");
        properties.setProperty("password", "root");
        Connection connect = driver.connect(url, properties);
        System.out.println(connect);
    }

    /**
     * 方式三:获取连接,使用DriverManager
     * @throws Exception
     */
    @Test
    public void testCon3() throws Exception{
        //1.获取Driver的实现类
        Class<?> aClass = Class.forName("com.mysql.cj.jdbc.Driver");
        Driver driver= (Driver) aClass.newInstance();
        //2.注册驱动
        DriverManager.registerDriver(driver);
        //3.获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_study?serverTimezone=GMT", "root", "root");
        System.out.println(connection);
    }

    /**
     * 方式四:优化方式三
     * @throws Exception
     */
    @Test
    public void testCon4() throws Exception{
        //1.加载驱动到内存中。在mysql的Driver实现类中声明了如下类的操作
        /**
         * static{
         *     java.sql.DriverManager.registerDriver(new Driver);
         * }
         */
        Class.forName("com.mysql.cj.jdbc.Driver");
        //2.获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_study?serverTimezone=GMT", "root", "root");
        System.out.println(connection);
    }

    /**
     * 方式五:优化方式四,将数据库获取连接所需的数据放在配置文件中
     * @throws Exception
     * 优点:换链接的时候只需要修改配置文件
     * 实现了解耦,可以避免程序的重新打包
     */
    @Test
    public void testCon5() throws Exception{
        Properties properties=new Properties();
        properties.load(this.getClass().getClassLoader().getResourceAsStream("jdbc.properties"));
        Class.forName(properties.getProperty("driver"));
        Connection connection = DriverManager.getConnection(properties.getProperty("url"), properties.getProperty("user"), properties.getProperty("password"));
        System.out.println(connection);

    }
}

2.2 PrepareStatement的update和query操作

2.2.1 update操作,即增删改操作

代码如下:

package com;

import org.junit.Test;
import utils.JdbcUtils;

import java.io.IOException;
import java.sql.*;
import java.text.SimpleDateFormat;
import java.util.Properties;

public class PreparedStatementUpdateTest {

    /**
     * preparedStatement增操作
     */
    @Test
    public void insertTest() {
        PreparedStatement preparedStatement = null;
        Connection connection = null;
        try {
            //1.获取一个链接
            Properties properties = new Properties();
            properties.load(ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties"));
            Class.forName(properties.getProperty("driver"));
            connection = DriverManager.getConnection(properties.getProperty("url"), properties.getProperty("user"), properties.getProperty("password"));
            //2.预编译sql语句获取preparedStatement对象
            preparedStatement = connection.prepareStatement("insert into customers(name,email,birth) values (?,?,?)");
            //3.填充占位符
            preparedStatement.setString(1, "zjx");
            preparedStatement.setString(2, "z@gmail.com");
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
            java.util.Date parse = simpleDateFormat.parse("2015-02-02");
            preparedStatement.setDate(3, new Date(parse.getTime()));
            //4.执行sql
            preparedStatement.execute();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //5.资源关闭
            try {
                if (preparedStatement != null) {
                    preparedStatement.close();
                }
                if (connection != null) {
                    connection.close();
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }

    /**
     * preparedStatement 改操作
     */
    @Test
    public void testUpdate(){
        Connection connection=null;
        PreparedStatement preparedStatement=null;
        try {
            connection = JdbcUtils.getConnection();
            preparedStatement = connection.prepareStatement("update customers set name =? where id=?");
            preparedStatement.setString(1,"cxw");
            preparedStatement.setInt(2,5);
            preparedStatement.execute();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                JdbcUtils.closeResource(connection,preparedStatement);
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }

    /**
     * 测试删除操作
     */
    @Test
    public void testDelete(){
        try {
            JdbcUtils.updateTable("delete from customers where id =?",5);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2.2.2 query即查询操作

使用到的实体类:
Customer

package entity;

import java.util.Date;

public class Customer {
    private Integer id;
    private String name;
    private String email;
    private Date birth;

    public Customer() {
    }

    @Override
    public String toString() {
        return "Customer{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", birth=" + birth +
                '}';
    }

    public Customer(Integer id, String name, String email, Date birth) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.birth = birth;
    }

    public Integer getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }

    public Date getBirth() {
        return birth;
    }

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

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

    public void setEmail(String email) {
        this.email = email;
    }

    public void setBirth(Date birth) {
        this.birth = birth;
    }
}

Order

package entity;

import java.sql.Date;

public class Order {
    private Integer orderId;
    private String orderName;
    private Date orderDate;

    @Override
    public String toString() {
        return "Order{" +
                "orderId=" + orderId +
                ", orderName='" + orderName + '\'' +
                ", orderDate=" + orderDate +
                '}';
    }

    public Order() {
    }

    public Order(Integer orderId, String orderName, Date orderDate) {
        this.orderId = orderId;
        this.orderName = orderName;
        this.orderDate = orderDate;
    }

    public void setOrderId(Integer orderId) {
        this.orderId = orderId;
    }

    public void setOrderName(String orderName) {
        this.orderName = orderName;
    }

    public void setOrderDate(Date orderDate) {
        this.orderDate = orderDate;
    }

    public Integer getOrderId() {
        return orderId;
    }

    public String getOrderName() {
        return orderName;
    }

    public Date getOrderDate() {
        return orderDate;
    }
}

操作封装:
JdbcUtils

package utils;

import entity.Customer;
import entity.Order;

import java.io.IOException;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * ORM编程思想(object relational mapping)
 * 一个数据表对应一个java类
 * 表中的一条记录对应类的一个对象
 * 表中的一个字段对应Java的一个属性
 */
public class JdbcUtils {
    /**
     * 获取数据库连接
     *
     * @return
     */
    public static Connection getConnection() throws Exception {
        Properties properties = new Properties();
        properties.load(ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties"));
        Class.forName(properties.getProperty("driver"));
        return DriverManager.getConnection(properties.getProperty("url"), properties.getProperty("user"), properties.getProperty("password"));
    }

    /**
     * 关闭资源
     *
     * @param connection
     * @param statement
     * @throws SQLException
     */
    public static void closeResource(Connection connection, PreparedStatement statement) throws SQLException {
        if (connection != null) {
            connection.close();
        }
        if (statement != null) {
            statement.close();
        }
    }

    public static void closeResource(Connection connection, PreparedStatement statement, ResultSet resultSet) throws SQLException {
        if (connection != null) {
            connection.close();
        }
        if (statement != null) {
            statement.close();
        }
        if (resultSet != null) {
            resultSet.close();
        }
    }

    /**
     * 增删改操作,统一封装
     *
     * @param sql
     * @param args
     * @throws Exception
     */
    public static void updateTable(String sql, Object... args) throws Exception {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        connection = getConnection();
        preparedStatement = connection.prepareStatement(sql);
        for (int i = 1; i < args.length + 1; i++) {
            preparedStatement.setObject(i, args[i - 1]);
        }
        preparedStatement.execute();
        closeResource(connection, preparedStatement);
    }

    public static Customer queryForCustomer(String sql, Object... args) throws Exception {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        connection = getConnection();
        preparedStatement = connection.prepareStatement(sql);
        for (int i = 0; i < args.length; i++) {
            preparedStatement.setObject(i + 1, args[i]);
        }
        resultSet = preparedStatement.executeQuery();
        //获取结果集中的元数据,这个结果集想想成一张表最为合适
        ResultSetMetaData metaData = resultSet.getMetaData();
        //通过metaData获取结果集中的列数
        int columnCount = metaData.getColumnCount();
        if (resultSet.next()) {
            Customer customer = new Customer();
            for (int i = 0; i < columnCount; i++) {
                //获取列的值
                Object object = resultSet.getObject(i + 1);
                //获取列的名称
                String columnName = metaData.getColumnName(i + 1);
                //通过反射注入值
                Field field = customer.getClass().getDeclaredField(columnName);
                field.setAccessible(true);
                field.set(customer, object);
            }
            closeResource(connection, preparedStatement, resultSet);
            return customer;
        }
        closeResource(connection, preparedStatement, resultSet);
        return null;
    }

    /**
     * 针对于表的字段名与类的属性名不同的情况:
     * 1.必须声明sql时,使用类的属性名来命名字段的别名
     * 2.使用ResultSetMeteData时,需要使用getColumnLabel()来替换getColumnName()来获取结果集的列名
     * 说明:如果sql中没有给字段起别名,getColumnLabel就是列名
     * <p>
     * 查询操作的流程:
     * 1.创建一个类对应到数据库中的表
     * 2.如果类的属性名与表的字段名相同则直接写sql,如果不同使用则写别名
     * 3.获取结果集的元数据 rs.getMetaData()
     * 4.获取结果集的列数rsmd.getColumnCount()
     * 5.获取一行一列的值rs.getObject(i)
     * 6.获取某一列的名字rsmd.getColumnLabel(index)
     * 7.使用反射注入对象中
     *
     * @param sql
     * @param args
     * @return
     * @throws Exception
     */
    public static Order queryForOrder(String sql, Object... args) throws Exception {
        Connection connection = getConnection();
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        for (int i = 0; i < args.length; i++) {
            preparedStatement.setObject(i + 1, args[i]);
        }
        ResultSet resultSet = preparedStatement.executeQuery();
        ResultSetMetaData metaData = resultSet.getMetaData();
        //获得多少列,反射创建对象遍历填充对象的值
        int columnCount = metaData.getColumnCount();
        if (resultSet.next()) {
            Order order = new Order();
            for (int i = 0; i < columnCount; i++) {
                Object object = resultSet.getObject(i + 1);
                String columnLabel = metaData.getColumnLabel(i + 1);
                Field declaredField = Order.class.getDeclaredField(columnLabel);
                declaredField.setAccessible(true);
                declaredField.set(order, object);
            }
            closeResource(connection, preparedStatement, resultSet);
            return order;
        }
        closeResource(connection, preparedStatement, resultSet);
        return null;
    }

    /**
     * 通用的查询操作,主要是使用反射,可以创建任意类的对象
     *
     * @param clazz
     * @param sql
     * @param args
     * @param <T>
     * @return
     * @throws Exception
     */
    public static <T> T queryForInstance(Class<T> clazz, String sql, Object... args) throws Exception {
        Connection connection = getConnection();
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        for (int i = 0; i < args.length; i++) {
            preparedStatement.setObject(i + 1, args[i]);
        }
        ResultSet resultSet = preparedStatement.executeQuery();
        ResultSetMetaData metaData = resultSet.getMetaData();
        int columnCount = metaData.getColumnCount();
        if (resultSet.next()) {
            T t = clazz.newInstance();
            for (int i = 0; i < columnCount; i++) {
                Object object = resultSet.getObject(i + 1);
                String columnLabel = metaData.getColumnLabel(i + 1);
                Field declaredField = clazz.getDeclaredField(columnLabel);
                declaredField.setAccessible(true);
                declaredField.set(t, object);
            }
            closeResource(connection, preparedStatement, resultSet);
            return t;
        }
        closeResource(connection, preparedStatement, resultSet);
        return null;
    }

    /**
     * 查询返回list
     * @param clazz
     * @param sql
     * @param args
     * @param <T>
     * @return
     *
     * PreparedStatement解决sql注入使用的是预编译,还有哪些好处?
     * 1.PreparedStatement操作Blob的数据,而statement做不到,利用占位符
     * 2.PreparedStatement可以实现更高效的批量操作
     */
    public static  <T> List<T> queryForList(Class<T> clazz, String sql, Object... args) {
        Connection connection=null;
        PreparedStatement preparedStatement=null;
        ResultSet resultSet=null;
        try {
            List<T> list = new ArrayList<T>();
            connection = getConnection();
            preparedStatement = connection.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
                preparedStatement.setObject(i + 1, args[i]);
            }
            resultSet = preparedStatement.executeQuery();
            ResultSetMetaData metaData = resultSet.getMetaData();
            int columnCount = metaData.getColumnCount();
            while (resultSet.next()) {
                T t = clazz.newInstance();
                for (int i = 0; i < columnCount; i++) {
                    Object object = resultSet.getObject(i + 1);
                    String columnLabel = metaData.getColumnLabel(i + 1);
                    Field declaredField = clazz.getDeclaredField(columnLabel);
                    declaredField.setAccessible(true);
                    declaredField.set(t, object);
                }
                list.add(t);
            }
            return list;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
                closeResource(connection, preparedStatement, resultSet);
            } catch (SQLException throwables) {
                throw new RuntimeException(throwables);
            }
        }
    }
}

2.3 插入及查询blob类型数据

package com;

import org.junit.Test;
import utils.JdbcUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.sql.*;

/**
 * 插入Blob类型数据
 * 只能使用preparedStatement进行blob类型数据的插入
 */
public class BlobTest {

    /**
     * 插入一张图片
     * @throws Exception
     */
    @Test
    public void insertImgTest() throws Exception {
        Connection connection = JdbcUtils.getConnection();
        String sql="insert into customers(name,email,birth,photo) values (?,?,?,?)";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setString(1,"hhh");
        preparedStatement.setString(2,"abc@qq.com");
        preparedStatement.setObject(3,"2018-05-12");
        preparedStatement.setBlob(4,new FileInputStream(new File("src\\main\\resources\\imgs\\bilibili.jpg")));
        preparedStatement.execute();
        JdbcUtils.closeResource(connection,preparedStatement);
    }

    /**
     * 查询数据库中的图片
     * 细节:当插入超过1M的图片的时候,需要到mysql文件夹下面找到 my.ini文件
     * 加入一句 max_allowed_packet=16M
     * 再重启服务才可以,默认最大打包是1M
     */
    @Test
    public void queryForImgTest(){
        Connection connection=null;
        PreparedStatement statement=null;
        ResultSet resultSet=null;
        try {
            connection=JdbcUtils.getConnection();
            statement=connection.prepareStatement("select photo from customers where id=7");
            resultSet = statement.executeQuery();
            if (resultSet.next()){
                Blob photo = resultSet.getBlob("photo");
                //得到一个二进制输入流
                InputStream binaryStream = photo.getBinaryStream();
                FileOutputStream stream=new FileOutputStream("src\\main\\resources\\imgs\\bilibili2.jpg");
                int len;
                byte[] buffs=new byte[1024];
                while ((len=binaryStream.read(buffs))!=-1){
                    stream.write(buffs,0,len);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                JdbcUtils.closeResource(connection,statement,resultSet);
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
}

2.3 批量插入操作

package com;

import org.junit.Test;
import utils.JdbcUtils;

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

/**
 * 使用preparedStatement
 * update delete本身就具有批量操作的效果
 * 此时的批量操作主要是批量插入,使用preparedStatement如何实现更高效的批量插入?
 */
public class BatchUpdateTest {

    /**
     * 批量操作1,就是使用for循环插入,算不上插入操作
     */
    @Test
    public void batchInsertTest1() {
        Connection connection = null;
        PreparedStatement statement = null;
        try {
            connection = JdbcUtils.getConnection();
            statement = connection.prepareStatement("insert into goods(name) values (?)");
            for (int i=0;i<20000;i++){
                statement.setString(1,"name_"+i);
                statement.execute();
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                JdbcUtils.closeResource(connection,statement);
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }

    /**
     * 批量插入方式二
     * 1.addBatch() executeBatch() clearBatch()
     * 2.需要开启批处理,需要在url后面加入 ?rewriteBatchedStatements=true
     */

    @Test
    public void batchInsertTest2() {
        Connection connection = null;
        PreparedStatement statement = null;
        try {
            connection = JdbcUtils.getConnection();
            statement = connection.prepareStatement("insert into goods(name) values (?)");
            for (int i=0;i<=20000;i++){
                statement.setString(1,"name_"+i);
                // 收集
                statement.addBatch();
                if (i%500==0){
                    //执行
                    statement.executeBatch();
                    //清空
                    statement.clearBatch();
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                JdbcUtils.closeResource(connection,statement);
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }

    /**
     * 关闭自动提交
     */
    @Test
    public void batchInsertTest3() {
        Connection connection = null;
        PreparedStatement statement = null;
        try {
            connection = JdbcUtils.getConnection();
            connection.setAutoCommit(false);
            statement = connection.prepareStatement("insert into goods(name) values (?)");
            for (int i=0;i<=20000;i++){
                statement.setString(1,"name_"+i);
                // 收集
                statement.addBatch();
                if (i%500==0){
                    //执行
                    statement.executeBatch();
                    //清空
                    statement.clearBatch();
                }
            }
            //提交数据
            connection.setAutoCommit(true);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                JdbcUtils.closeResource(connection,statement);
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
}

3.数据库连接池

3.1 c3p0数据库连接池

需要引入坐标:

<dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
</dependency>

使用配置文件配置数据库:
1.必须命名为c3p0-config.xml
2.内容要使用规定标准

c3p0-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <named-config name="c3p0test">
<!--        创建数据库的四个基本信息-->
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbc_study?serverTimezone=GMT</property>
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="user">root</property>
        <property name="password">root</property>

<!--        进行数据库连接池管理的基本信息-->
<!--        当数据库连接池的数目不够了依次向数据库申请的连接数,这边一次申请5个-->
        <property name="acquireIncrement">5</property>
<!--        c3p0数据库连接池中初始化时的连接数-->
        <property name="initialPoolSize">10</property>
<!--        最小的池子中的连接数-->
        <property name="minPoolSize">10</property>
<!--        维护的最大连接数-->
        <property name="maxPoolSize">100</property>
<!--        维护的最多的statement的个数,执行sql语句-->
        <property name="maxStatements">200</property>
<!--        每个连接中可以最多使用的statement的个数-->
        <property name="maxStatementsPerConnection">2</property>
<!--        <property name="checkoutTimeout">30000</property>-->
<!--        <property name="idleConnectionTestPeriod">30</property>-->
<!--        <property name="maxIdleTime">30</property>-->
    </named-config>
</c3p0-config>

创建数据库连接池,获取连接:

package test;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;

import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.SQLException;

public class C3p0Test {
    //方式一
    @Test
    public void testGetConnection() throws PropertyVetoException, SQLException {
        //获取c3p0数据库连接池
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/jdbc_study?serverTimezone=GMT&rewriteBatchedStatements=true");
        dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
        dataSource.setUser("root");
        dataSource.setPassword("root");
        //通过设置参数对数据库连接池进行管理
        //设置初始时数据库连接池中的连接数
        dataSource.setInitialPoolSize(10);
        Connection connection = dataSource.getConnection();
        System.out.println(connection);
    }

    //方式二 使用配置文件
    @Test
    public void test2() throws SQLException {
        ComboPooledDataSource dataSource=new ComboPooledDataSource("c3p0test");
        Connection connection = dataSource.getConnection();
        System.out.println(connection);
    }
}

3.2 DBCP数据库连接池

需要引入pom坐标:

		<dependency>
            <groupId>commons-dbcp</groupId>
            <artifactId>commons-dbcp</artifactId>
            <version>1.4</version>
        </dependency>
        <dependency>
            <groupId>commons-pool</groupId>
            <artifactId>commons-pool</artifactId>
            <version>1.5.4</version>
        </dependency>

dbcp.properties

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc_study?serverTimezone=GMT&rewriteBatchedStatements=true
username=root
password=root

代码:

package test;

import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import org.junit.Test;

import javax.sql.DataSource;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

public class DBCPTest {
    /**
     * 测试DBCP的数据库连接池技术获取连接
     * 方式一:不推荐
     */
    @Test
    public void testGetConnection() throws SQLException {
        //创建了DBCP的数据库连接池
        BasicDataSource dataSource=new BasicDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/jdbc_study?serverTimezone=GMT");
        dataSource.setUsername("root");
        dataSource.setPassword("root");

        //还可以设置涉及其他数据库连接池管理的相关属性
        dataSource.setInitialSize(10);
        //最大活跃数
        dataSource.setMaxActive(10);
        Connection connection = dataSource.getConnection();
        System.out.println(connection);
    }

    //方式二:使用配置文件
    @Test
    public void getConnection2() throws Exception {
        Properties pros = new Properties();
        pros.load(ClassLoader.getSystemClassLoader().getResourceAsStream("dbcp.properties"));
        DataSource dataSource = BasicDataSourceFactory.createDataSource(pros);
        Connection connection = dataSource.getConnection();
        System.out.println(connection);
    }
}

3.3 Druid数据库连接池

pom引入坐标:

 		<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.1</version>
        </dependency>

配置文件:
druid.properties

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc_study?serverTimezone=GMT&rewriteBatchedStatements=true
username=root
password=root

代码:

package test;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.junit.Test;

import javax.sql.DataSource;
import java.sql.Connection;
import java.util.Properties;

public class DruidTest {
    /**
     * 使用配置文件
     */
    @Test
    public void test() throws Exception {
        Properties pros=new Properties();
        pros.load(ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties"));
        DataSource dataSource = DruidDataSourceFactory.createDataSource(pros);
        Connection connection = dataSource.getConnection();
        System.out.println(connection);
    }
}

3.4 使用三种数据库连接池获取连接

public class JDBCUtils {
    //数据库连接池提供一个即可
    private static ComboPooledDataSource dataSource; //c3p0
    private static DataSource dataSource2;//dbcp
    private static DataSource dataSource3;//druid

    static {
        dataSource = new ComboPooledDataSource("c3p0test");
        Properties pros = new Properties();
        try {
            pros.load(ClassLoader.getSystemClassLoader().getResourceAsStream("dbcp.properties"));
            dataSource2 = BasicDataSourceFactory.createDataSource(pros);
        } catch (Exception e) {
            e.printStackTrace();
        }
        Properties pros1 = new Properties();
        try {
            pros1.load(ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties"));
            dataSource3 = DruidDataSourceFactory.createDataSource(pros1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 从c3p0中获取连接
     *
     * @return
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

    /**
     * 从DBCP中获取连接
     *
     * @return
     */
    public static Connection getConnection2() throws Exception {
        return dataSource2.getConnection();
    }

    public static Connection getConnection3() throws SQLException {
        return dataSource3.getConnection();
    }
}

3.5 DaoImpl中传入字节码参数,使用子类继承父类时泛型替代

public abstract class BaseDao<T> {
    Class<T> clazz=null;
	//注意这里的代码****************
    //获取当前BaseDao的子类继承的父类中的泛型
    {
        Type genericSuperclass = this.getClass().getGenericSuperclass();
        System.out.println(genericSuperclass);
        ParameterizedType parameterizedType= (ParameterizedType) genericSuperclass;
        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
        clazz= (Class<T>) actualTypeArguments[0];
    }
    /**
     * 获取数据库连接
     *
     * @return
     */
    public static Connection getConnection() throws Exception {
        Properties properties = new Properties();
        properties.load(ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties"));
        Class.forName(properties.getProperty("driver"));
        return DriverManager.getConnection(properties.getProperty("url"), properties.getProperty("user"), properties.getProperty("password"));
    }

    /**
     * 关闭资源
     *
     * @param connection
     * @param statement
     * @throws SQLException
     */
    public static void closeResource(Connection connection, PreparedStatement statement) throws SQLException {
        if (connection != null) {
            connection.close();
        }
        if (statement != null) {
            statement.close();
        }
    }

    public static void closeResource(Connection connection, PreparedStatement statement, ResultSet resultSet) throws SQLException {
        if (connection != null) {
            connection.close();
        }
        if (statement != null) {
            statement.close();
        }
        if (resultSet != null) {
            resultSet.close();
        }
    }

    /**
     * 考虑事务之后的更新操作
     *
     * @param sql
     * @param args
     * @throws Exception
     */
    public static void updateTransaction(Connection connection, String sql, Object... args) throws Exception {
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        for (int i = 1; i < args.length + 1; i++) {
            preparedStatement.setObject(i, args[i - 1]);
        }
        preparedStatement.execute();
        closeResource(null, preparedStatement);
    }

    /**
     * 考虑事务后的查询操作,通用的查询操作,返回一个对象
     *
     * @param sql
     * @param args
     * @return
     * @throws Exception
     */
    public  T queryTransactionForInstance(Connection connection, String sql, Object... args) throws Exception {
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        for (int i = 0; i < args.length; i++) {
            preparedStatement.setObject(i + 1, args[i]);
        }
        ResultSet resultSet = preparedStatement.executeQuery();
        ResultSetMetaData metaData = resultSet.getMetaData();
        int columnCount = metaData.getColumnCount();
        if (resultSet.next()) {
            T t = clazz.newInstance();
            for (int i = 0; i < columnCount; i++) {
                Object object = resultSet.getObject(i + 1);
                String columnLabel = metaData.getColumnLabel(i + 1);
                Field declaredField = clazz.getDeclaredField(columnLabel);
                declaredField.setAccessible(true);
                declaredField.set(t, object);
            }
            closeResource(null, preparedStatement, resultSet);
            return t;
        }
        closeResource(null, preparedStatement, resultSet);
        return null;
    }

    /**
     * 考虑事务之后查询返回list
     *
     * @param sql
     * @param args
     * @return PreparedStatement解决sql注入使用的是预编译,还有哪些好处?
     * 1.PreparedStatement操作Blob的数据,而statement做不到,利用占位符
     * 2.PreparedStatement可以实现更高效的批量操作
     */
    public  List<T> queryForList(Connection connection, String sql, Object... args) {
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            List<T> list = new ArrayList<T>();
            preparedStatement = connection.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
                preparedStatement.setObject(i + 1, args[i]);
            }
            resultSet = preparedStatement.executeQuery();
            ResultSetMetaData metaData = resultSet.getMetaData();
            int columnCount = metaData.getColumnCount();
            while (resultSet.next()) {
                T t = clazz.newInstance();
                for (int i = 0; i < columnCount; i++) {
                    Object object = resultSet.getObject(i + 1);
                    String columnLabel = metaData.getColumnLabel(i + 1);
                    Field declaredField = clazz.getDeclaredField(columnLabel);
                    declaredField.setAccessible(true);
                    declaredField.set(t, object);
                }
                list.add(t);
            }
            return list;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
                closeResource(null, preparedStatement, resultSet);
            } catch (SQLException throwables) {
                throw new RuntimeException(throwables);
            }
        }
    }

    /**
     * 查询特殊值
     * @param connection
     * @param sql
     * @param args
     * @param <E>
     * @return
     */
    public static <E> E queryForValue(Connection connection, String sql, Object... args) {
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            preparedStatement = connection.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
                preparedStatement.setObject(i + 1, args[i]);
            }
            resultSet = preparedStatement.executeQuery();
            if (resultSet.next()) {
                return (E) resultSet.getObject(1);
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            try {
                closeResource(null, preparedStatement, resultSet);
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        return null;
    }
}

4.DBUtils

4.1 DBUtils增删改查

pom

 <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.7</version>
        </dependency>

java代码

package utils.test;

import entity.Customer;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.handlers.*;
import org.junit.Test;
import utils.JDBCUtils;

import java.sql.Connection;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;

public class JdbcDBUtilsTest {
    //插入测试,增删改 都一样
    @Test
    public void testInsert() {
        QueryRunner runner = new QueryRunner();
        Connection connection = null;
        String sql = "update `account` set name=? where id=?";
        try {
            connection = JDBCUtils.getConnection();
            int ddd = runner.update(connection, sql, "ddd", 1);
            System.out.println(ddd);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            try {
                JDBCUtils.CloseConnection(connection);
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }

    /**
     * 使用的是BeanHandler,返回一个Bean对象,用于封装表中的一个记录
     */
    @Test
    public void testSelect1(){
        QueryRunner runner = new QueryRunner();
        Connection connection = null;
        String sql = "select name,email,birth from customers where id=?";
        try {
            connection = JDBCUtils.getConnection();
            Customer query = runner.query(connection, sql, new BeanHandler<Customer>(Customer.class), 1);
            System.out.println(query);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            try {
                JDBCUtils.CloseConnection(connection);
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }

    /**
     * 使用的是BeanListHandler,多个Bean对象构成的集合
     */
    @Test
    public void testSelect2(){
        QueryRunner runner = new QueryRunner();
        Connection connection = null;
        String sql = "select name,email,birth from customers where id>?";
        try {
            connection = JDBCUtils.getConnection();
            List<Customer> query = runner.query(connection, sql, new BeanListHandler<Customer>(Customer.class), 1);
            query.forEach(System.out::println);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            try {
                JDBCUtils.CloseConnection(connection);
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }

    /**
     * 使用MapHandler,一个map对应一条查询记录,将字段和相应字段的值作为key和value
     */
    @Test
    public void testSelect3(){
        QueryRunner runner = new QueryRunner();
        Connection connection = null;
        String sql = "select name,email,birth from customers where id=?";
        try {
            connection = JDBCUtils.getConnection();
            //将一条数据以键值对封装 {name="王菲",键=值,键=值,键=值}
            Map<String, Object> query = runner.query(connection, sql, new MapHandler(), 1);
            System.out.println(query);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            try {
                JDBCUtils.CloseConnection(connection);
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }

    /**
     * MapListHandler 由Map封装而成的List
     * ArrayListHandler 由Array封装而成的List
     */
    @Test
    public void testSelect4(){
        QueryRunner runner = new QueryRunner();
        Connection connection = null;
        String sql = "select name,email,birth from customers where id>?";
        try {
            connection = JDBCUtils.getConnection();
            //将一条数据以键值对封装 {name="王菲",键=值,键=值,键=值}
            List<Map<String, Object>> query = runner.query(connection, sql, new MapListHandler(), 1);
            query.forEach(System.out::println);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            try {
                JDBCUtils.CloseConnection(connection);
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }

    /**
     * ScalarHandler用于查询特殊值
     */
    @Test
    public void testSelect5(){
        QueryRunner runner = new QueryRunner();
        Connection connection = null;
        String sql = "select count(*) from customers";
        try {
            connection = JDBCUtils.getConnection();
            Long query = runner.query(connection, sql, new ScalarHandler<Long>());
            System.out.println(query);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            try {
                JDBCUtils.CloseConnection(connection);
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }

    /**
     * 自定义handler
     */
    @Test
    public void testSelect6(){
        QueryRunner runner = new QueryRunner();
        Connection connection = null;
        String sql = "select name,email,birth from customers where id=?";
        try {
            connection = JDBCUtils.getConnection();
            ResultSetHandler<Customer> resultSetHandler=new ResultSetHandler<Customer>() {
                @Override
                public Customer handle(ResultSet resultSet) throws SQLException {
                    System.out.println("自定义handler ");
                    if (resultSet.next()){
                        String name = resultSet.getString("name");
                        Date birth = resultSet.getDate("birth");
                        String email = resultSet.getString("email");
                        return new Customer(name,email,birth);
                    }
                    return null;
                }
            };
            Customer query = runner.query(connection, sql, resultSetHandler, 1);
            System.out.println(query);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            try {
                JDBCUtils.CloseConnection(connection);
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值