当前位置: 首页 > java>阅读正文

Java连接数据库技术-JDBC参考文档

2021.12.2 朱丰华 1185 次 留下评论 24466字

jdbc 全称为 java database connectivity,是 java 操作关系型数据库的技术。

jdbc 的本质是一套操作关系型数据库的接口,由sun公司制定接口,由各个数据库厂商实现接口提供实现的驱动 jar 包。这样做的好处是,程序员只需要学会jdbc,就可以操作任何关系型数据库,而不用关心操作各个数据库之间区别。

JDBC API主要位于JDK中的java.sql包中(之后扩展的内容位于javax.sql包中),主要包括(斜体代表接口,需驱动程序提供者来具体实现):

  • DriverManager:负责加载各种不同驱动程序(Driver),并根据不同的请求,向调用者返回相应的数据库连接(Connection)。
  • Driver:驱动程序,会将自身加载到DriverManager中去,并处理相应的请求并返回相应的数据库连接(Connection)
  • Connection:数据库连接,负责与进行数据库间通讯,SQL执行以及事务处理都是在某个特定Connection环境中进行的。可以产生用以执行SQL的Statement。
  • Statement:用以执行SQL查询和更新(针对静态SQL语句和单次执行)。
  • PreparedStatement:用以执行包含动态参数的SQL查询和更新(在服务器端编译,允许重复执行以提高效率)。
  • CallableStatement:用以调用数据库中的存储过程
  • SQLException:代表在数据库连接的建立和关闭和SQL语句的执行过程中发生了例外情况(即错误)。

数据类型映射

一般来说,SQL类型与Java数据类型转换是比较固定的:

SQL类型Java类型
CHARjava.lang.String
VARCHARjava.lang.String
LONGVARCHARjava.lang.String
NUMERICjava.math.BigDecimal
DECIMALjava.math.BigDecimal
BITboolean
TINYINTbyte
SMALLINTshort
INTEGERint
BIGINTlong
REALfloat
FLOATdouble
DOUBLEdouble
BINARYbyte[]
VARBINARYbyte[]
LONGVARBINARYbyte[]
DATEjava.sql.Date
TIMEjava.sql.Time
TIMESTAMPjava.sql.Timestamp
BLOBjava.sql.Blob
CLOBjava.sql.Clob
Arrayjava.sql.Array
REFjava.sql.Ref
Structjava.sql.Struct

注释:这种类型匹配不是强制性标准,特定的JDBC厂商可能会改变这种类型匹配。例如Oracle中的DATE类型是包含时分秒,而java.sql.Date仅仅支持年月日,此时应该用java.sql.Timestamp。

连接实例

使用jdbc连接数据库,一般需要以下几个步骤

  1. 导入jar包
  2. 注册驱动
  3. 获取Connection对象
  4. 编写sql
  5. 获取sql的执行对象
  6. 执行sql,并返回结果
  7. 处理结果
  8. 释放连接对象

以下,使用mysql5.7进行连接,jar包下载:mysql-connector-java-5.1.26.jar

首先创建一个user表,插入一些数据,然后使用java通过jdbc获取

create database jdbc;
use jdbc;
create table if not exists user(
    id int(20) primary key auto_increment,
    name varchar(30) default null,
    pwd varchar(30) default null
)engine innodb default character set utf8;
insert into user(name,pwd) values('张三','123'),('李四','456'),('王五','789');

创建一个测试类,测试连接

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class Test {

    public static final String URL = "jdbc:mysql://localhost:3306/jdbc?useUnicode=true&characterEncoding=UTF-8";
    public static final String USER = "root";
    public static final String PASSWORD = "123456";

    public static void main(String[] args) throws Exception {
        //1.加载驱动程序
        Class.forName("com.mysql.jdbc.Driver");
        //2. 获得数据库连接
        Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
        //3.操作数据库,实现增删改查
        Statement stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery("SELECT * FROM user");
        //如果有数据,rs.next()返回true
        while(rs.next()){
                System.out.println("账号:"+rs.getString("name")+",密码:"+rs.getInt("pwd"));
        }
    }
}

运行该程序,执行结果如下:

账号:张三,密码:123
账号:李四,密码:456
账号:王五,密码:789

JDBC对象详解

JDBC是面向对象的,在上面的概述以及例子中,JDBC的许多对象没有进行详细的解释,我们需要学习常用的类

DriverManager对象

此对象,是驱动管理器,全限定类名java.sql.DriverManager,在jdbc中使用的除了注册时的Driver,使用的都是java.sql包下的类。

在实例化时,我们使用了class.forName来进行注册,我们也可以使用DriverManager进行注册,例如:

DriverManager.registerDriver(new com.mysql.jdbc.Driver());

查看Driver的源码

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

我们发现在静态代码块中它实际上就执行了上述的register并new了一个Driver,当然new com.mysql.jdbc.Driver()的功能也是一样的,因为静态代码块只会执行一次,只要它执行了目的就达到了。在new对象时可以导包或指定全限定类名,而实际上驱动会自动注册,在java.sql.DriverManager源代码中有这样一句话,所以更多的时候我们是在重复注册。

 * <P>Applications no longer need to explicitly load JDBC drivers using <code>Class.forName()</code>. Existing programs
 * which currently load JDBC drivers using <code>Class.forName()</code> will continue to work without
 * modification.

在获取连接对象时,我们也使用到了DriverManager

Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);

使用它获取连接对象时,是比较繁琐的,每次都需要配置driver,user,password,后续将学习JDBCUtils简化操作

Connection对象

此对象,是数据库连接对象,它可以用于获取Sql执行对象

  • createStatement()
  • prepareStatement(String sql)

或进行事务管理

  • 开启事务:setAutoCommit(boolean autoCommit);
  • 提交事务:commit();
  • 回滚事务:rollback();

在jdbc中,默认是执行一条就立刻提交,也就是没有开启事务,如果要手动开启事务应该关闭自动提交。以下是一个简单的事务处理流程

try{
    ...
    //开启事务
    conn.setAutoCommit(false);
    ...
    //提交事务
    conn.commit();
}catch (Exception e){
    //事务回滚
    try {
        if(conn != null) {
            conn.rollback();
        }
    } catch (SQLException e1) {
        e1.printStackTrace();
    }
    e.printStackTrace();
}

Statement对象

它是sql的执行对象,使用它进行sql的执行

  • boolean execute(String sql); 可执行任意sql
  • int executeUpdate(String sql); 执行DML,DDL
  • ResultSet executeQuery(String sql); 执行DQL语句

对于不同的数据类型,会得到不同的结果,根据返回值我们看到,执行update时返回的是int,query则返回ResultSet类型

ResultSet对象

ResultSet并不是Set的子类,它是一个接口。它是一个结果集,包含了多条数据,是 sql 查询到的所有数据

每次操作一条数据,通过移动游标不断获取数据

  • boolean rs.next(); 向后移动一条数据,(它同时返回一个 boolean)
  • XXX getXXX(“name”); 获取 XXX 类型的数据,名称为 name

Preparestatement对象

此对象是预编译sql对象,它与Statement对象不同的是,它可以执行动态sql来防止sql注入

  • 用 ? 作为占位符,编译时,再换成真正的字符,这样更安全
  • 在执行前,对 ? 通过 pstmt.setXXX( index, value);赋值

例如,以下实例使用了预编译sql对象

import java.sql.*;

public class Test {

    public static final String URL = "jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8";
    public static final String USER = "jdbc";
    public static final String PASSWORD = "123456";

    public static void main(String[] args) throws Exception {
        //1.加载驱动程序
        Class.forName("com.mysql.jdbc.Driver");
        //2. 获得数据库连接
        Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
        //3.操作数据库,实现增删改查
        PreparedStatement stmt = conn.prepareStatement("select * from user where name=?");
        stmt.setString(1,"张三");
        ResultSet rs = stmt.executeQuery();
        //如果有数据,rs.next()返回true
        while(rs.next()){
            System.out.println("账号:"+rs.getString("name")+",密码:"+rs.getInt("pwd"));
        }
    }

}

程序执行结果如下:

账号:张三,密码:123456

JDBCUtils工具类

在使用jdbc时,一般会创建一个工具类以简化连接操作。

在src下创建一个properties文件,用于保存连接配置,例如以下是mysql8.0

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/union?useSSL=false&serverTimezone=UTC
user=root
password=root

接着,编写一个JDBCUtils类,代码如下:

import java.io.FileReader;
import java.net.URL;
import java.sql.*;
import java.util.Properties;


public class JDBCUtils {
	private static String driver;
	private static String url;
	private static String user;
	private static String password;

	//静态字符块,自动加载资源文件
	static {
                //获取src路径下的文件的方式--->ClassLoader 类加载器
                ClassLoader classLoader = JDBCUtils.class.getClassLoader();
                //获取资源对象(得到一个 url 对象)
                URL res  = classLoader.getResource("jdbc.properties");
                //得到资源真实路径
                String path = res.getPath();
                //创建Properties类型的对象
                Properties p =new Properties();
	        try {
			//加载资源文件
			p.load(new FileReader(path));
			driver = p.getProperty("driver");
			url = p.getProperty("url");
		        user = p.getProperty("user");
			password = p.getProperty("password");
			//加载驱动

			Class.forName(driver);
			System.out.println("驱动加载成功!");
        	} catch (Exception e) {
			System.out.println("驱动加载失败!");
			e.printStackTrace();
		}
       	}
        //获得连接对象的方法
	public static  Connection getConnection() {
		try{
		        System.out.println("数据库连接成功");
	        	return DriverManager.getConnection(url, user, password);
		}
		catch(Exception e) {
			System.out.println("数据库连接失败");
			e.printStackTrace();
		}
		return null;
	}
	//释放连接资源
	public static void close(Connection conn, Statement statement,ResultSet result) {

		try {
			if(result!=null) {
				result.close();
				result=null;
			}
			if(statement!= null) {
				 statement.close();
				statement=null;
			}
			if(conn!=null) {
				conn.close();
				conn=null;
			}
		}
		catch(Exception e) {
			e.printStackTrace();
		}
	}
}

此工具类,能一键获取连接,并快速释放资源,简化了jdbc操作。

JDBC连接池

前面我们使用的Connection对象是一次性的,在每次使用时创建,在用完后销毁,但对于大量的sql操作时这样的方式是非常浪费资源的。

连接池是一种技术,也是一个对象,它保存了一些Connection对象,在每次获取Connection时从连接池获取,释放时则归还给连接池。

连接池技术也是sun公司制定的,它是javax.sql.DataSource。它本身也是一个接口,并且由数据库厂商实现连接池,程序员只需要会使用即可。

DataSource对象,有2个常用方法

  • getConnection(); 获取连接
  • close(); 归还连接

数据库连接池使用起来并没有太大的区别,而仅仅是获取Connection与释放Connection稍有不同

常用的jdbc连接池,有C3p0、DBCP、Druid等连接池

C3p0连接池

C3p0是一款开源的数据库连接池技术,它成名较早,Hibernate将其作为内置的数据库连接池,简单易用 稳定性好,一些维护的老项目可能使用的比较多,现在看来性能比较差。

C3p0需要导入2个jar包,

注释:使用连接池时,仍然需要导入数据库驱动jar包

C3p0的配置定义,有一些强制性

  • 名称:c3p0.properties 或者 c3p0-config.xml
  • 路径:放在 src 下即可,会自动读取
<c3p0-config>
  <!-- 使用默认的配置读取连接池对象 -->
  <default-config>
  	<!--  连接参数 -->
    <property name="driverClass">com.mysql.jdbc.Driver</property>
    <property name="jdbcUrl">jdbc:mysql://localhost:3306/day25</property>
    <property name="user">root</property>
    <property name="password">root</property>
    
    <!-- 连接池参数,初始化数量,最大数量,连接失败超时时间 -->
    <property name="initialPoolSize">5</property>
    <property name="maxPoolSize">10</property>
    <property name="checkoutTimeout">3000</property>
  </default-config>

  <named-config name="otherc3p0"> 
    <!--  连接参数 -->
    <property name="driverClass">com.mysql.jdbc.Driver</property>
    <property name="jdbcUrl">jdbc:mysql://localhost:3306/day25</property>
    <property name="user">root</property>
    <property name="password">root</property>
    
    <!-- 连接池参数 -->
    <property name="initialPoolSize">5</property>
    <property name="maxPoolSize">8</property>
    <property name="checkoutTimeout">1000</property>
  </named-config>
</c3p0-config>

C3p0获取连接池对象,使用ComboPooledDataSource,没有参数时使用默认配置,可以指定配置参数

  • DataSource ds = new ComboPooledDataSource();
  • DataSource ds = new ComboPooledDataSource(“otherc3p0”);

Druid连接池

Druid连接池,是阿里巴巴开源的jdbc连接池技术,性能卓越。

使用时,仍然需要同时导入 连接池jar数据库驱动jar,下载jar包:druid-1.0.9.jar

Druid的配置文件,也有一些强制规定

  • 必须是 properties 类型的文件
  • 名称任意,可以放置在任意地方(假设为 druid.properties)
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/db3
username=root
password=root
initialSize=5
maxActive=10
maxWait=3000

获取连接池对象

// 加载properties
Properties pro = new Properties();
InputStream is = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties");
pro.load(is);
// 创建连接池对象 
DataSource ds = DruidDataSourceFactory.createDataSource(pro);

我们发现,Druid连接池比C3p0稍微麻烦一点,当然我们可以通过创建DruidUtils简化,不过我们仍然命名为JDBCUtils,假设配置文件名为 druid.properties,那么JDBCUtils代码如下:

import com.alibaba.druid.pool.DruidDataSourceFactory;

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

/**
 * Druid连接池的工具类
 */
public class JDBCUtils {

    //定义成员变量 DataSource
    private static DataSource ds ;

    static{
        try {
            //1.加载配置文件
            Properties pro = new Properties();
            pro.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
            //2.获取DataSource
            ds = DruidDataSourceFactory.createDataSource(pro);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取连接池方法
     */

    public static DataSource getDataSource(){
        return  ds;
    }
    
    /**
     * 获取连接方法
     */
    public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }

    //归还连接(2参数)
    public static void close(Statement stmt,Connection conn){
    	
       close(null,stmt,conn);
    }


    //归还连接(3参数)
    public static void close(ResultSet rs , Statement stmt, Connection conn){

        if(rs != null){
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if(stmt != null){
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if(conn != null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

数据库相关Maven坐标

Mysql

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

Oracle没有坐标,附jar包下载:ojdbc6.jar

C3p0

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

Druid

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

高级封装

一般的JDBCUtils,是简化数据库的连接,仅仅是getConnection,而并没有针对sql语句进行简化。

Spring的JdbcTemplate,是一个很好的封装例子,能一条语句获取sql结果。

JdbcTemplate使用

它是Spring框架的一个基础Jdbc工具,手动导入比较麻烦需要多个jar包,使用maven时,只需要以下坐标

   <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.0.8.RELEASE</version>
    </dependency>

以mysql、druid配合jdbctemplate为例手动导入jar包,导入以下jar包:

  • commons-logging-1.2.jar
  • druid-1.0.9.jar
  • mysql-connector-java-5.1.26.jar
  • spring-beans-5.0.0.RELEASE.jar
  • spring-core-5.0.0.RELEASE.jar
  • spring-jdbc-5.0.0.RELEASE.jar
  • spring-tx-5.0.0.RELEASE.jar

为此,这里制作了一个压缩包可直接下载,jdbctemplate-jar.zip

基本实例

在导入jar包后,你仍然需要配置数据库连接,以及数据库连接池信息,在这里必须使用数据库连接池

这里省略不必要的步骤,假设你已经配置了连接池,JdbcUtils.getDataSource()方法返回数据库连接池实例,那么本实例代码如下:

import com.zhufenghua.util.JdbcUtils;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.List;
import java.util.Map;

public class Main {

    public static void main(String[] args){
        JdbcTemplate template = new JdbcTemplate(JdbcUtils.getDataSource());

        List<Map<String, Object>> maps = template.queryForList("select * from user");
        for (Map<String, Object> map : maps) {
            System.out.println(map);
        }
    }
}

运行该代码,返回一个maps对象,它根据查询到的字段,全部封装到map中,执行结果例如:

{id=1, name=张三, pwd=123456}
{id=2, name=李四, pwd=}
{id=3, name=王五, pwd=}
{id=4, name=哈哈, pwd=123}
{id=5, name=哈哈, pwd=123}

增删改操作

jdbctemplate,进行增删改操作,只需要使用update方法。

语法解释:

int count = template.update(sql,Object...args);

参数sql,是prepareStatement的sql,存在参数时使用?替代,如果存在参数,那么在sql之后的args是可变参数,根据顺序传递实际参数即可,例如以下实例:

public class Main {

    public static void main(String[] args){
        JdbcTemplate template = new JdbcTemplate(JdbcUtils.getDataSource());

        print(template);
        int update = template.update("insert into user(name,pwd) values(?,?)", "abc", "123");
        if(update==1){
            System.out.println("新增用户成功");
        }else{
            System.out.println("新增用户失败");
        }
        print(template);
    }
    public static void print(JdbcTemplate template){
        System.out.println("******");
        List<Map<String, Object>> maps = template.queryForList("select * from user");
        for (Map<String, Object> map : maps) {
            System.out.println(map);
        }
    }
}

查询操作

查询操作常用方法:

  • 列行单列:
    • queryForObject(); 查询结果,根据传递的class,返回对应的Object
  • 单行结果:
    • queryForMap(); 获取一行数据,自动封装为Map<String,Object>
    • query();获取一行或多行数据,指定class<T>,封装为List<T>
  • 多行结果:
    • queryForList(); 获取一行或多行数据,无参数,封装为List<Map<String,Object>>

封装为Map时,会自动获取数据库返回的字段,而封装为Object时,会匹配对象的属性名称,如果对象属性不符合ORM规则会封装不到该字段数据。也就是说封装为Map比封装为Object兼容性更强,前者不会出错,后者可能取不到数据。

上面的代码缺少参数为此,我们编写一个实例:

    public static void main(String[] args){
        JdbcTemplate template = new JdbcTemplate(JdbcUtils.getDataSource());

        //单行单列,封装为基础对象,使用其引用类型
        Long count = template.queryForObject("select count(*) from user",Long.class);
        System.out.println("单行单列,封装为基础对象:");
        System.out.println(count);

        //单行,封装为自定义对象
        User user1 = template.queryForObject("select * from user where id=?", new BeanPropertyRowMapper<User>(User.class), 1);
        System.out.println("单行,封装为自定义对象:");
        System.out.println(user1);

        //单行,封装为Map
        Map<String, Object> stringObjectMap = template.queryForMap("select * from user where id=?", 2);
        System.out.println("单行,封装为Map对象:");
        System.out.println(stringObjectMap);

        //多行,封装为自定义对象
        List<User> users = template.query("select * from user",new BeanPropertyRowMapper<User>(User.class));
        System.out.println("多行,封装为自定义对象List集合");
        for (User user : users) {
            System.out.println(user);
        }
        //多行,封装为Map
        List<Map<String, Object>> maps = template.queryForList("select * from user");
        System.out.println("多行,封装为Map的List集合");
        for (Map<String, Object> map : maps) {
            System.out.println(map);
        }

    }

JdbcTemplate对比JDBCUtils的优势在于,前者能够一句代码直接从数据库得到数据,极大的简化了数据库操作,对于简单的不涉及事务操作的增删改查,几乎不需要编写dao。

ViewJDBC案例

模仿JdbcTemplate,尝试手动封装一键执行的jdbc工具类,尝试在升级JDBCUtils以达到JdbcTemplate的使用效果,或更完善其功能,并命名为JdbcDeepUtils。基本目标:

  • 可选择的使用 数据库连接池
  • 基础Update语句
    • 一键增、删、改
    • 支持事务控制的增、删、改
  • 基础Select语句
    • 查询一行,封装为Object
    • 查询一行,封装为Map
    • 查询多行,封装为Object的List集合
    • 查询多行,封装为Map的List集合
    • 查询单行单列,封装为基础对象
    • 基础Select支持复用Connection
  • 高级语句
    • 例如,一键分页查询
    • 注:针对各个库的实现可能有所不同,主要支持Mysql

因为c3p0配置太简单,且其效率不如Druid,这里默认封装Druid连接池,如果不使用Druid,或更换连接池,仅需修改导包名以及更换获取连接池语,其他不变。编写jdbc.properties文件复制如下:

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis
username=jdbc
password=ET54dLzyekAprddM
initialSize=5
maxActive=10
maxWait=3000

编写ViewJDBC.java,复制如下代码即可:

package com.zhufenghua.util;


import java.lang.reflect.Field;
import java.sql.*;
import java.util.*;
import com.alibaba.druid.pool.DruidDataSourceFactory;

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


/**
 *           -- 基本介绍 --
 * 高级Jdbc工具类封装,支持原生jdbc以及Druid连接池
 * 使用此模块,不使用连接池,还需要:1.导入驱动,2.编写配置文件
 * 若使用Druid连接池,还导入Druid.jar
 *
 *
 *           -- 功能介绍 --
 * ①基础语句
 * update:简化、复用  |  通用的insert, delete, update语句
 * queryForBase:简化、复用 | 查询基础对象
 * queryForObject:简化、复用 | 查询自定义Object
 * queryForMap:简化、复用  |  查询为String,Object
 * queryObjectList:简化、复用 | 查询为List<Object>
 * queryMapList:简化、复用 | 查询为List<Map<String,Object>
 * ②高级语句, 均为简化语句
 * insert:插入一行数据,并返回自增的ID
 * in:简化的insert | 插入一行数据,并返回自增的ID
 * queryForPage:分页查询  | 包含数据和总数,以MapList形式返回,最后一个Map为总数
 * getPage: 简化的分页查询 | 包含数据和总数,以MapList形式返回,最后一个Map为总数
 *
 *
 *          -- 关于Map与Object --
 * 设计了Map与Object两种格式,其中:
 * Map格式为原格式,100%兼容,包含返回的所有字段
 * Object使用反射原理,仅封装对象存在的属性字段,如果属性格式和实际返回格式不兼容,无法转换时值为空
 * 分页时,仅支持Map格式,也就是不支持指定class而是返回原始数据。
 */
public class ViewJDBC {

    // properties配置文件名
    private static final String proName = "jdbc.properties";

    // 基础连接信息
    private static String url;
    private static String username;
    private static String password;

    //是否使用数据库连接池
    private static  Boolean useDs;
    // 数据库连接池对象
    private static DataSource ds ;

    public static int DBType;
    public static int Mysql = 1;  // 预设常量,指定数据库类型
    public static int Oracle = 2;  // 表示Oracle

    /*
     * 自动初始化,默认不使用连接池,可手动改true
     */
    static{
        init(false,Mysql);
    }
    /**
     * 使用前必须初始化
     * @param useDs 是否使用Drud连接池
     * @param DBType 表示数据库类型
     */
    public static void init(boolean useDs,int DBType){
        ViewJDBC.useDs = useDs;
        try {
            //1.加载配置文件
            Properties config = new Properties();
            config.load(ViewJDBC.class.getClassLoader().getResourceAsStream(proName));
            //2.加载配置
            if(useDs){
                ds = DruidDataSourceFactory.createDataSource(config);
            }else{
                //基本连接信息
                String driver = config.getProperty("driverClassName");
                url = config.getProperty("url");
                username = config.getProperty("username");
                password = config.getProperty("password");
                Class.forName(driver);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        ViewJDBC.DBType = DBType;
    }

    /**
     * 获取连接池
     */
    public static DataSource getDataSource(){
        return ds;
    }

    /**
     * 获取连接方法
     */
    public static Connection getConnection(){
        Connection conn = null;
        try {
            if(useDs){
                conn = ds.getConnection();
            }else{
                conn =  DriverManager.getConnection(url, username, password);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return conn;
    }

    /**
     * 归还连接,1参数
     */
    public static void close(Connection conn){
        close(conn,null,null);
    }

    /**
     * 归还连接,2参数
     */
    public static void close(Statement stmt, ResultSet rs){
        close(null,stmt, rs);
    }

    /**
     * 归还连接,3参数
     */
    public static void close(Connection conn,Statement stmt, ResultSet rs){

        if(rs != null){
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if(stmt != null){
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if(conn != null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 支持复用的update
     */
    public static int update(Connection conn,String sql,Object...args){
        int rs=0;
        PreparedStatement ps = null;
        try{
            ps = conn.prepareStatement(sql);
            for (int i = 0; i <args.length; i++) {
                ps.setObject(i+1, args[i]);
            }
            rs = ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            close(ps,null);
        }
        return rs;
    }

    /**
     * 简化的update
     */
    public static int update(String sql,Object...args){
        Connection conn = getConnection();
        int rs = update(conn,sql,args);
        close(conn);
        return rs;
    }


    /**
     * 支持复用的queryForBase(基础对象)
     * @param conn 连接对象
     * @param clazz 基础对象的class
     * @param sql 语句
     * @param args 参数
     * @param <T> 泛型
     * @return 基础对象
     */
    public static <T> T queryForBase(Connection conn,Class<T> clazz,String sql,Object...args){
        PreparedStatement ps = null;
        ResultSet rs = null;
        T t = null;
        try{
            ps = conn.prepareStatement(sql);
            for (int i = 0; i <args.length; i++) {
                ps.setObject(i+1, args[i]);
            }
            rs = ps.executeQuery();
            if(rs.next()){
                String name = rs.getMetaData().getColumnName(1);
                t  = (T)rs.getObject(name);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            close(ps,rs);
        }
        return t;
    }



    /**
     * 简化的QueryForBase
     */
    public static <T> T queryForBase(Class<T> clazz,String sql,Object...args){
        Connection conn = getConnection();
        T t = queryForBase(conn, clazz, sql, args);
        close(conn);
        return t;
    }


    /**
     * ResultSet 转 Object
     */
    private static <T> void rsToObject(ResultSet rs,Class<T> clazz,Object t) throws SQLException, IllegalAccessException {
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            String name = field.getName();
            String type = field.getType().toString();
            Object object = rs.getObject(name);
            boolean accessible = field.isAccessible();
            field.setAccessible(true);
            //兼容int
            if (type.equals("int")) {
                int anInt = rs.getInt(name);
                field.set(t, anInt);
            }
            //兼容java.sql.Date
            else if (type.equals("class java.sql.Date")) {
                field.set(t, rs.getDate(name));
            }
            //判断格式是否兼容,否则不赋值
            else if (field.getType().isAssignableFrom(object.getClass())) {
                field.set(t, object);
            }
            field.setAccessible(accessible);
        }
    }



    /**
     * 支持复用的queryForObject(自定义对象)
     * 提示:根据类的属性名获取,名称错误或类型不兼容导致空值
     */
    public static <T> T queryForObject(Connection conn, Class<T> clazz,String sql,Object...args){
        T t = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = conn.prepareStatement(sql);
            for (int i = 0; i <args.length; i++) {
                ps.setObject(i+1, args[i]);
            }
            rs = ps.executeQuery();
            t = clazz.newInstance();
            if(rs.next()){
                rsToObject(rs,clazz,t);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            close(ps,rs);
        }
        return t;
    }

    public static <T> T queryForObject(Class<T> clazz,String sql,Object...args){
        Connection conn = getConnection();
        T t = queryForObject(conn, clazz, sql, args);
        close(conn);
        return t;
    }


    /**
     * 把rs列名和值送入map的key和value中
     * @param rs 原ResultSet类
     * @param map 目标map
     * @throws SQLException 调用者处理
     */
    private static void rsToMap(ResultSet rs,Map<String,Object> map) throws SQLException {
        ResultSetMetaData rsMd = rs.getMetaData();
        for (int i = 0; i < rsMd.getColumnCount(); i++) {
            String name = rsMd.getColumnName(i+1);
            name = name.toLowerCase();
            Object object = rs.getObject(name);
            map.put(name,object);
        }
    }


    /**
     * 支持复用的queryForMap
     * @param conn 连接对象
     * @param sql 预执行的sql
     * @param args 参数
     * @return Map
     */

    public static Map<String,Object> queryForMap(Connection conn, String sql, Object... args){
        Map<String,Object> map = new HashMap<>();
        PreparedStatement ps = null;
        ResultSet rs = null;
        try{
            ps = conn.prepareStatement(sql);
            for (int i = 0; i <args.length; i++) {
                ps.setObject(i+1, args[i]);
            }
            rs = ps.executeQuery();
            if(rs.next()){
                rsToMap(rs,map);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            close(ps,rs);
        }
        return map;
    }


    /**
     * 简化的queryForMap,查询一行数据封装为Map,兼容性极好
     */
    public static Map<String,Object> queryForMap(String sql, Object... args){
        Connection conn = getConnection();
        Map<String,Object> map = queryForMap(conn,sql,args);
        close(conn);
        return map;
    }

    /**
     * queryObjectList,多行封装为实体类List集合,如果属性名称或属性类型错误导致空值
     */
    public static <T> List<T> queryObjectList(Connection conn, Class<T> clazz, String sql, Object...args){
        List<T> list = new ArrayList<>();
        PreparedStatement ps = null;
        ResultSet rs = null;
        try{
            ps = conn.prepareStatement(sql);
            for (int i = 0; i <args.length; i++) {
                ps.setObject(i+1, args[i]);
            }
            rs = ps.executeQuery();
            while (rs.next()){
                T t = clazz.newInstance();
                rsToObject(rs,clazz,t);
                list.add(t);
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            close(ps,rs);
        }
        return list;
    }


    /**
     * 简化的queryObjectList
     */
    public static <T> List<T> queryObjectList(Class<T> clazz, String sql, Object...args){
        Connection conn = getConnection();
        List<T> list = queryObjectList(conn, clazz, sql, args);
        close(conn);
        return list;
    }




    /**
     * 支持复用的queryMapList
     * @param conn 连接对象
     * @param sql 预执行的sql
     * @param args 参数
     * @return MapList集合
     */
    public static List<Map<String,Object>> queryMapList(Connection conn, String sql, Object... args){
        List<Map<String,Object>> list = new ArrayList<>();
        PreparedStatement ps = null;
        ResultSet rs = null;
        try{
            ps = conn.prepareStatement(sql);
            for (int i = 0; i <args.length; i++) {
                ps.setObject(i+1, args[i]);
            }
            rs = ps.executeQuery();
            while (rs.next()){
                Map<String,Object> map = new HashMap<>();
                rsToMap(rs,map);
                list.add(map);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            close(ps,rs);

        }
        return list;
    }


    /**
     * queryMapList,使用List的map集合返回原始数据,兼容性极好
     */
    public static List<Map<String,Object>> queryMapList(String sql, Object... args){
        Connection conn =  getConnection();
        List<Map<String,Object>> list = queryMapList(conn,sql,args);
        close(conn);
        return list;
    }


    /**
     * 插入数据,并进行一次Base查询
     * @param sql 插入数据的sql
     * @param sqlBase 查询Base的sql
     * @param clazz 泛型引用
     * @param args 插入sql的参数
     * @param <T> 指定Base的泛型
     * @return 如果插入成功,返回Base,如果插入失败返回null
     */
    public static <T> T insert(String sql, String sqlBase, Class<T> clazz, Object...args){
        Connection conn = getConnection();
        int update = update(conn, sql, args);
        T t = null;
        if(update==1){
            t = queryForBase(conn, clazz, sqlBase);
        }
        return t;
    }


    /**
     * 预设的插入并获取主键方式,适用于函数获取自增ID的数据库
     */
    public static Long in(String sql, Object...args){
        Connection conn = getConnection();
        long update = update(conn, sql, args);
        if(update==1){
            if(DBType==Mysql){
                update = queryForBase(conn, Long.class, "select LAST_INSERT_ID() id");
            }
        }
        return update;
    }

    /**
     * 基础分页查询,包含数据信息以及总数信息,类似queryMapList,最后一个Map为总数
     * @param sql 分页查询的sql
     * @param sqlSum 查询总数的sql,无参或与sql同参数
     * @param sumArgs 如果true,sqlSum使用sql相同参数
     * @param args  分页查询sql的参数
     * @return 前N-1个Map是分页原始数据,最后一个Map为总数
     */
    public static List<Map<String,Object>> queryForPage(String sql,String sqlSum, boolean sumArgs, Object...args){
        Connection conn =  getConnection();

        List<Map<String,Object>> list = queryMapList(conn,sql,args);
        Map<String,Object> map = null;
        if(sumArgs){
            map = queryForMap(conn,sqlSum, args);
        }else{
            map = queryForMap(conn,sqlSum);
        }
        list.add(map);
        close(conn);
        return list;
    }

    /**
     * 不定参数转list
     * @param args 不定参数
     * @return 转换后的ArrayList
     */
    private static List argsList(Object...args){
        List list = new ArrayList();
        for (int i = 0; i < args.length; i++) {
            list.add(args[i]);
        }
        return list;
    }

    /**
     * 简化的分页查询
     * @param pageNum 要获取第几页
     * @param pageSize 一页中要几条数据
     * @param sql 预设sql,保持原查询,如 select * from paper 结尾不可写分号
     * @param args 对应的sql参数
     * @return 类似queryMapList,返回页面数据,最后一个Map代表pageInfo对象
     */
    public static List<Map<String,Object>> getPage(long pageNum, long pageSize, String sql,Object...args){
        List<Map<String,Object>> mapList = null;
        long start = (pageNum-1) * pageSize;
        Connection conn = getConnection();
        if(DBType==Mysql){
            String key = "select";
            StringBuilder sb = new StringBuilder(sql);
            sb.insert(sql.indexOf(key)+key.length()," sql_calc_found_rows ");
            sb.append(" limit ?,?");
            String pre_sql = sb.toString();
            List list = argsList(args);
            list.add(start);
            list.add(pageSize);
            Object[] objects = list.toArray();
            mapList = queryMapList(conn,pre_sql, objects);
            // pageInfo封装
            Map<String,Object> pageInfo = new HashMap<>();
            pageInfo.put("pageNum",pageNum);
            pageInfo.put("pageSize",pageSize);
            // 获取结果条数
            long total = queryForBase(conn,Long.class,"SELECT FOUND_ROWS()");
            pageInfo.put("total",total);
            // 计算总页数
            long pages = (long) Math.ceil(total *1.0 / pageSize);
            pageInfo.put("pages",pages);
            // 当前页数据量
            long size = mapList.size();
            pageInfo.put("size",size);
            // 页面判断
            pageInfo.put("isFirstPage",pageNum == 1);
            pageInfo.put("isLastPage",pageNum == pages);
            pageInfo.put("hasPrePage",pageNum>1);
            pageInfo.put("hasNextPage",pageNum<pages);
            pageInfo.put("prePage",pageNum-1);
            pageInfo.put("nextPage",pageNum+1);
            mapList.add(pageInfo);
        }
        close(conn);
        return mapList;
    }

    /**
     * 模块测试
     */
    public static void main(String[] args){

        // 第2页,一页3行
        System.out.println(getPage(2,3,"select * from user"));
    }

}

本篇完,还有疑问?留下评论吧

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注