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类型 |
---|---|
CHAR | java.lang.String |
VARCHAR | java.lang.String |
LONGVARCHAR | java.lang.String |
NUMERIC | java.math.BigDecimal |
DECIMAL | java.math.BigDecimal |
BIT | boolean |
TINYINT | byte |
SMALLINT | short |
INTEGER | int |
BIGINT | long |
REAL | float |
FLOAT | double |
DOUBLE | double |
BINARY | byte[] |
VARBINARY | byte[] |
LONGVARBINARY | byte[] |
DATE | java.sql.Date |
TIME | java.sql.Time |
TIMESTAMP | java.sql.Timestamp |
BLOB | java.sql.Blob |
CLOB | java.sql.Clob |
Array | java.sql.Array |
REF | java.sql.Ref |
Struct | java.sql.Struct |
注释:这种类型匹配不是强制性标准,特定的JDBC厂商可能会改变这种类型匹配。例如Oracle中的DATE类型是包含时分秒,而java.sql.Date仅仅支持年月日,此时应该用java.sql.Timestamp。
连接实例
使用jdbc连接数据库,一般需要以下几个步骤
- 导入jar包
- 注册驱动
- 获取Connection对象
- 编写sql
- 获取sql的执行对象
- 执行sql,并返回结果
- 处理结果
- 释放连接对象
以下,使用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"));
}
}
本篇完,还有疑问?留下评论吧