目录
- 在Java中,数据库存取技术可分为如下几类:
-
JDBC直接访问数据库
-
JDO (Java Data Object )技术
-
第三方O/R工具,如Hibernate, Mybatis 等
-
JDBC是java访问数据库的基石,JDO、Hibernate、MyBatis等只是更好的封装了JDBC。
JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口(一组API),定义了用来访问数据库的标准Java类库,(java.sql,javax.sql)使用这些类库可以以一种标准的方法、方便地访问数据库资源。
JDBC为访问不同的数据库提供了一种统一的途径,为开发者屏蔽了一些细节问题。
连接数据库
java.sql.Driver 接口是所有 JDBC 驱动程序需要实现的接口。这个接口是提供给数据库厂商使用的(提供一个规范),不同数据库厂商提供不同的实现。
连接数据库需要4个基本信息:数据库厂商提供的driver实现类(JDBC 驱动),数据库URL,用户名,密码
MySQL 8.0 以下版本 - JDBC 驱动名及数据库 URL
static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost:3306/db";
MySQL 8.0 以上版本 - JDBC 驱动名及数据库 URL
static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost:3306/db?useSSL=false";
查看MySQL时区:show variables like ‘%time_zone%’;
设置MySQL时区:方式1. 在 [mysqld] 下面加上 default-time-zone=’+08:00’
方式2. set global time_zone=’+08:00’;,重启
方式一:
public void Connection() {
Connection con = null;
try {
String url = "jdbc:mysql://localhost:3306/test?useSSL=false";
String user = "root";
String password = "123456";
String driverClass = "com.mysql.cj.jdbc.Driver";
//加载驱动
Class.forName(driverClass);
//获取连接
con = DriverManager.getConnection(url, user, password);
System.out.println(con);
} catch (SQLException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
if (con != null)
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
原本还需要在加载驱动后面注册驱动,DriverManager.registerDriver(driver);。但在加载MySQL的jdbc的Driver类时已注册,所以该步骤可以省略。
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
方式二:将连接数据库的基本信息声明在配置文件中,通过读取配置文件的方式获取连接。
好处:1. 实现数据与代码分离,降低数据的耦合。(只要修改配置文件的信息,就可以在不修改代码的基础上使用其他的数据库)
2. 如果修改配置信息,可以省去重新编译代码的过程,提高可移植性。
配置文件声明在工程的src目录下:jdbc.properties
url=jdbc:mysql://localhost:3306/test?useSSL=false
user=root
password=123456
driverClass=com.mysql.cj.jdbc.Driver
public void Connection() {
Connection con = null;
InputStream stream = null;
try {
//读取配置文件的基本信息
stream = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
Properties properties = new Properties();
properties.load(stream);
String url = properties.getProperty("url");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driverClass = properties.getProperty("driverClass");
//加载驱动
Class.forName(driverClass);
//获取连接
con = DriverManager.getConnection(url, user, password);
System.out.println(con);
} catch (SQLException | ClassNotFoundException | IOException e) {
e.printStackTrace();
} finally {
try {
if (con != null)
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (stream != null)
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
使用PreparedStatement实现CRUD操作
在 java.sql 包中有 3 个接口分别定义了对数据库的调用的不同方式:
- Statement:用于执行静态 SQL 语句并返回它所生成结果的对象。
- PrepatedStatement:SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。
- CallableStatement:用于执行 SQL 存储过程
Statement的弊端
通过调用 Connection 对象的 createStatement() 方法创建该对象。该对象用于执行静态的 SQL 语句,并且返回执行结果。
但是使用Statement操作数据表存在弊端:1. 存在拼串操作,繁琐。2. 存在SQL注入问题。
SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令(如:SELECT user, password FROM user_table WHERE user=‘1’ OR ’ AND password = ’ =1 OR ‘1’ = ‘1’) ,从而利用系统的 SQL 引擎完成恶意行为的做法。
对于 Java 而言,要防范 SQL 注入,只要用 PreparedStatement(从Statement扩展而来) 取代 Statement 就可以了。
PreparedStatement的使用
PreparedStatement 接口是 Statement 的子接口,它可以预编译 SQL 语句。
PreparedStatement 能最大可能提高性能:
- DBServer会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。
- 在statement语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身不能匹配,没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存。这样每执行一次都要对传入的语句编译一次。(更高效的批量操作)
- (语法检查,语义检查,翻译成二进制命令,缓存)
- PreparedStatement 可以防止 SQL 注入。以 SELECT user, password FROM user_table WHERE user=‘1’ OR ’ AND password = ’ =1 OR ‘1’ = ‘1’; 为例,如果使用Statement,这条语句可以看成 user=‘1’,OR,’ AND password = ’ =1,OR,‘1’ = ‘1’;显然该语句变成了OR关系,最后一个条件是一定成立的,所以会出现问题。
而PreparedStatement可以使用占位符?,所以上面的语句可以写出 SELECT user, password FROM user_table WHERE user=? AND password =?; 。PreparedStatement可以预编译SQL语句,在编译的时候就确定了上面的AND关系。1’ OR , =1,OR,‘1’ = ‘1’ 分别填充占位符,无论怎么改都不会变成OR关系,所以解决了SQL注入。 - PreparedStatement可以操作Blob类型数据,Statement不可以
Java与SQL对应数据类型转换表
Java类型 | SQL类型 |
---|---|
boolean | BIT |
byte | TINYINT |
short | SMALLINT |
int | INTEGER |
long | BIGINT |
String | CHAR,VARCHAR,LONGVARCHAR |
byte array | BINARY , VAR BINARY |
java.sql.Date | DATE |
java.sql.Time | TIME |
java.sql.Timestamp | TIMESTAMP |
增删改的通用操作
public static void update(String sql, Object... args) {
Connection con = null;
PreparedStatement ps = null;
try {
//获取连接
con = JDBCUtils.Connection();
//预编译SQL语句,返回PreparedStatement对象
ps = con.prepareStatement(sql);
//填充占位符
int index = 1;
for (Object object : args) {
ps.setObject(index, object);
index++;
}
//执行SQL
ps.execute();
//ps.executeUpdate(); 有返回值
} catch (SQLException e) {
e.printStackTrace();
} finally {
//关闭资源
JDBCUtils.closeResource(con, ps);
}
}
查询的通用操作
ResultSet 对象以逻辑表格的形式封装了执行数据库操作的结果集,ResultSet 接口由数据库厂商提供实现。
ResultSet 对象维护了一个指向当前数据行的游标,初始的时候,游标在第一行之前,可以通过 ResultSet 对象的 next() 方法移动到下一行。调用 next()方法检测下一行是否有效。若有效,该方法返回 true,且指针下移。相当于Iterator对象的 hasNext() 和 next() 方法的结合体。
ResultSetMetaData可用于获取关于 ResultSet 对象中列的类型和属性信息的对象。
注意:
- 其中采用了ORM思想(object relational mapping),结果集可以用Javabean存储。
一个数据表对应一个java类
表中的一条记录对应java类的一个对象
表中的一个字段对应java类的一个属性 - 写SQL语句时,查询字段名字要与Bean中的属性名一致,如果不一致需要起别名。
- Java与数据库交互涉及到的相关Java API中的索引都从1开始。
public static <T> List<T> select(Class<T> clazz, String sql, Object... args) {
Connection con = null;
PreparedStatement ps = null;
ResultSet resultSet = null;
try {
//获取连接
con = JDBCUtils.Connection();
//预编译SQL语句,返回PreparedStatement对象
ps = con.prepareStatement(sql);
//填充占位符
int index = 1;
for (Object object : args) {
ps.setObject(index, object);
index++;
}
//执行SQL并获取返回集
resultSet = ps.executeQuery();
//获取结果集的元数据
ResultSetMetaData rsmd = resultSet.getMetaData();
//通过元数据获取结果集的列数
int columnCount = rsmd.getColumnCount();
//迭代获取行数据
List<T> list = new ArrayList<T>();
while (resultSet.next()) {
T t = clazz.getConstructor().newInstance();
//处理结果集的每一行数据
for (int i = 1; i <= columnCount; i++) {
//获取列值
Object columnValue = resultSet.getObject(i);
//获取列的别名
//String columnName = rsmd.getColumnName(i);
String columnLabel = rsmd.getColumnLabel(i);
//通过反射给t对象指定columnLabel列,赋值为columnValue
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, columnValue);
}
list.add(t);
}
return list;
} catch (SQLException | InstantiationException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException | NoSuchFieldException e) {
e.printStackTrace();
} finally {
//关闭资源
JDBCUtils.closeResource(con, ps, resultSet);
}
return null;
}
释放资源
数据库连接(Connection)是非常稀有的资源,用完后必须马上释放,如果Connection不能及时正确的关闭将导致系统宕机。Connection的使用原则是尽量晚创建,尽量早的释放。
可以在finally中关闭,保证及时其他代码出现异常,资源也一定能被关闭。
public static void closeResource(Connection con, PreparedStatement ps) {
try {
if (con != null)
con.close();
if (ps != null)
ps.close();
System.out.println("Resource Closed");
} catch (SQLException e) {
e.printStackTrace();
}
}
public static void closeResource(Connection con, PreparedStatement ps, ResultSet resultSet) {
try {
if (con != null)
con.close();
if (ps != null)
ps.close();
if (resultSet != null)
resultSet.close();
System.out.println("Resource Closed");
} catch (SQLException e) {
e.printStackTrace();
}
}
插入与查询Blob类型
插入BLOB类型的数据必须使用PreparedStatement,因为BLOB类型的数据无法使用字符串拼接写的。
如果存储的文件过大,数据库的性能会下降。如果在指定了相关的Blob类型以后,还报错:xxx too large,那么在mysql的安装目录下,找my.ini文件加上如下的配置参数: max_allowed_packet=16M。同时注意:修改了my.ini文件之后,需要重新启动mysql服务。
插入Blob类型数据:
public void insertBlob() {
Connection con = null;
PreparedStatement ps = null;
try {
con = JDBCUtils.Connection();
String sql = "insert into customers(name,email,birth,photo) values(?,?,?,?);";
ps = con.prepareStatement(sql);
ps.setObject(1, "Jack");
ps.setObject(2, "Jack@gmail.com");
ps.setObject(3, "1999-9-9");
FileInputStream fis = new FileInputStream(new File("123.jpg"));
ps.setObject(4, fis);
int row = ps.executeUpdate();
if (row > 0) {
System.out.println("Successful");
} else {
System.out.println("Failed");
}
fis.close();
} catch (SQLException | IOException e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(con, ps);
}
}
查询Blob类型数据:
public void selectBlob() {
Connection con = null;
PreparedStatement ps = null;
try {
con = JDBCUtils.Connection();
String sql = "select photo from customers where id = ?;";
ps = con.prepareStatement(sql);
ps.setObject(1, 21);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
Blob blob = rs.getBlob("photo");
InputStream is = blob.getBinaryStream();
FileOutputStream fos = new FileOutputStream(new File("666.jpg"));
byte[] bufffer = new byte[1024];
int len;
while ((len = is.read(bufffer)) != -1) {
fos.write(bufffer, 0, len);
}
fos.close();
}
} catch (SQLException | IOException e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(con, ps);
}
}
批量操作
mysql服务器默认是关闭批处理的,我们需要通过一个参数,让mysql开启批处理的支持。?rewriteBatchedStatements=true 写在配置文件的url后面
public void batch() {
Connection con = null;
PreparedStatement ps = null;
try {
long start = System.currentTimeMillis();
con = JDBCUtils.Connection();
//设置不允许自动提交
con.setAutoCommit(false);
String sql = "insert into goods(name) values(?)";
ps = con.prepareStatement(sql);
for (int i = 1; i <= 500000; i++) {
ps.setString(1, "" + i);
ps.addBatch();
if(i % 1000 == 0) {
ps.executeBatch();
ps.clearBatch();
}
}
//提交数据
con.commit();
System.out.println("cost time: " + (System.currentTimeMillis() - start)); //7746
} catch (SQLException e) {
e.printStackTrace();
} finally {
con.setAutoCommit(true);
JDBCUtils.closeResource(con, ps);
}
}
DAO及相关实现类
DAO:Data Access Object访问数据信息的类和接口,包括了对数据的CRUD(Create、Retrival、Update、Delete),而不包含任何业务相关的信息。有时也称作:BaseDAO。作用:为了实现功能的模块化,更有利于代码的维护和升级。
// 封装对数据表的通用操作
public abstract class DAO<T> {
// 定义一个变量来接收泛型T的类型
private Class<T> clazz = null;
// 获取T的Class对象,获取泛型的类型,泛型是在被子类继承时才确定
{
// getGenericSuperclass()用来获取当前类的父类的类型
Type genericSuperclass = this.getClass().getGenericSuperclass();
// ParameterizedType表示的是带泛型的类型
ParameterizedType param = (ParameterizedType) genericSuperclass;
// getActualTypeArguments获取具体的泛型的类型
Type[] types = param.getActualTypeArguments();
// 获取具体的泛型的类型·
clazz = (Class<T>) types[0];
}
/**
* 增删改(考虑事务)
* @param sql
* @param args
*/
public int update(Connection con, String sql, Object... args) {
PreparedStatement ps = null;
try {
//预编译SQL语句,返回PreparedStatement对象
ps = con.prepareStatement(sql);
//填充占位符
int index = 1;
for (Object object : args) {
ps.setObject(index, object);
index++;
}
//执行SQL
return ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//关闭资源
JDBCUtils.closeResource(null, ps);
}
return 0;
}
/**
* 查询获取结果集
* @param con 考虑事务,连接不能断
* @param clazz 结果集对象
* @param sql
* @param args
* @return
*/
public List<T> select(Connection con, String sql, Object... args) {
PreparedStatement ps = null;
ResultSet resultSet = null;
try {
//预编译SQL语句,返回PreparedStatement对象
ps = con.prepareStatement(sql);
//填充占位符
int index = 1;
for (Object object : args) {
ps.setObject(index, object);
index++;
}
//执行SQL并获取返回集
resultSet = ps.executeQuery();
//获取结果集的元数据
ResultSetMetaData rsmd = resultSet.getMetaData();
//通过元数据获取结果集的列数
int columnCount = rsmd.getColumnCount();
//迭代获取行数据
List<T> list = new ArrayList<T>();
while (resultSet.next()) {
T t = clazz.getConstructor().newInstance();
//处理结果集的每一行数据
for (int i = 1; i <= columnCount; i++) {
//获取列值
Object columnValue = resultSet.getObject(i);
//获取列的别名
//String columnName = rsmd.getColumnName(i);
String columnLabel = rsmd.getColumnLabel(i);
//通过反射给t对象指定columnLabel列,赋值为columnValue
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, columnValue);
}
list.add(t);
}
return list;
} catch (SQLException | InstantiationException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException | NoSuchFieldException e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(null, ps, resultSet);
}
return null;
}
}
接口定义要实现的方法:
public interface CustomerDAO {
void insert(Connection con, Customer customer);
void delete(Connection con, Customer customer);
void update(Connection con, Customer customer);
void select(Connection con);
}
public class CustomerDAOImpl extends DAO<Customer> implements CustomerDAO {
public void insert(Connection con, Customer customer) {
String sql = "insert into customers(name,email,birth) values(?,?,?)";
update(con, sql, customer.getName(), customer.getEmail(), customer.getBirth());
}
public void delete(Connection con, Customer customer) {
String sql = "delete from customers where id=?";
update(con, sql, customer.getId());
}
public void update(Connection con, Customer customer) {
String sql = "update customers set name=? where id=?";
update(con, sql, customer.getName(), customer.getId());
}
public void select(Connection con) {
String sql = "select id,name,email,birth from customers";
List<Customer> list = select(con, sql);
list.forEach(System.out :: println);
}
}
数据库连接池
普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用。若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。
这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。
数据库连接池的基本思想:就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
当数据库访问结束后,程序还是像以前一样关闭数据库连接:connclose(); 但con.close()并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。
数据库连接池技术的优点
1. 资源重用
由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。
2. 更快的系统反应速度
数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间
3. 新的资源分配手段
对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源
4. 统一的连接管理,避免数据库连接泄漏
在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露
JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,具体由其它第三方类实现。
Druid数据库连接池:
配置 | 缺省 | 说明 |
---|---|---|
name | 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this) | |
url | 连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto | |
username | 连接数据库的用户名 | |
password | 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/使用ConfigFilter | |
driverClassName | 根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下) | |
initialSize | 0 | 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 |
maxActive | 8 | 最大连接池数量 |
maxIdle | 8 | 已经不再使用,配置了也没效果 |
minIdle | 最小连接池数量 | |
maxWait | 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 | |
poolPreparedStatements | false | 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。 |
maxOpenPreparedStatements | -1 | 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100 |
validationQuery | 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。 | |
testOnBorrow | true | 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 |
testOnReturn | false | 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 |
testWhileIdle | false | 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 |
timeBetweenEvictionRunsMillis | 有两个含义: 1)Destroy线程会检测连接的间隔时间2)testWhileIdle的判断依据,详细看testWhileIdle属性的说明 | |
numTestsPerEvictionRun | 不再使用,一个DruidDataSource只支持一个EvictionRun | |
minEvictableIdleTimeMillis | ||
connectionInitSqls | 物理连接初始化的时候执行的sql | |
exceptionSorter | 根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接 | |
filters | 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall | |
proxyFilters | 类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系 |
src下的配置文件为:druid.properties
url=jdbc:mysql://localhost:3306/test?useSSL=false
username=root
password=123456
driverClassName=com.mysql.cj.jdbc.Driver
initialSize=10
maxActive=10
public class druidTest {
//使用Druid数据库连接技术
private static DataSource source;
//静态代码块只执行一次,完成初始操作,只建立一个数据库连接池
static {
InputStream is = null;
try {
Properties pros = new Properties();
is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
pros.load(is);
source = DruidDataSourceFactory.createDataSource(pros);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static Connection getConnection() throws SQLException {
Connection con = source.getConnection();
return con;
}
}
Apache-DBUtils实现CRUD操作
DbUtils :提供如关闭连接、装载JDBC驱动程序等常规工作的工具类,里面的所有方法都是静态的。主要方法如下:
- public static void close(…) throws java.sql.SQLException: DbUtils类提供了三个重载的关闭方法。这些方法检查所提供的参数是不是NULL,如果不是的话,它们就关闭Connection、Statement和ResultSet。
- public static void closeQuietly(…): 这一类方法不仅能在Connection、Statement和ResultSet为NULL情况下避免关闭,还能隐藏一些在程序中抛出的SQLEeception。
- public static void commitAndClose(Connection conn)throws SQLException: 用来提交连接的事务,然后关闭连接
- public static void commitAndCloseQuietly(Connection conn): 用来提交连接,然后关闭连接,并且在关闭连接时不抛出SQL异常。
- public static void rollback(Connection conn)throws SQLException:允许conn为null,因为方法内部做了判断
- public static void rollbackAndClose(Connection conn)throws SQLException
- rollbackAndCloseQuietly(Connection)
- public static boolean loadDriver(java.lang.String driverClassName):这一方装载并注册JDBC驱动程序,如果成功就返回true。使用该方法,你不需要捕捉这个异常ClassNotFoundException。
QueryRunner类简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。
- 更新:public int update(Connection conn, String sql, Object… params) throws SQLException:用来执行一个更新(插入、更新或删除)操作。
- 插入:public T insert(Connection conn,String sql,ResultSetHandler rsh, Object… params) throws SQLException:只支持INSERT语句,其中 rsh - The handler used to create the result object from the ResultSet of auto-generated keys. 返回值: An object generated by the handler.即自动生成的键值
- 批处理:public int[] batch(Connection conn,String sql,Object[][] params)throws SQLException: INSERT, UPDATE, or DELETE语句
public T insertBatch(Connection conn,String sql,ResultSetHandler rsh,Object[][] params)throws SQLException:只支持INSERT语句 - 查询:public Object query(Connection conn, String sql, ResultSetHandler rsh,Object… params) throws SQLException:执行一个查询操作,在这个查询中,对象数组中的每个元素值被用来作为查询语句的置换参数。该方法会自行处理 PreparedStatement 和 ResultSet 的创建和关闭。
public void testInsert() throws Exception {
QueryRunner runner = new QueryRunner();
Connection conn = JDBCUtils.getConnByDruid();
String sql = "insert into customers(name,email,birth)values(?,?,?)";
int count = runner.update(conn, sql, "Jack", "Jack@qq.com", "1998-08-08");
System.out.println("添加了" + count + "条记录");
JDBCUtils.closeResource(conn, null);
}
ResultSetHandler接口的主要实现类:
- ArrayHandler:把结果集中的第一行数据转成对象数组。
- ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
- BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
- BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
- ColumnListHandler:将结果集中某一列的数据存放到List中。
- KeyedHandler(name):将结果集中的每一行数据都封装到一个Map里,再把这些map再存到一个map里,其key为指定的key。
- MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
- MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
- ScalarHandler:查询单个值对象
获取多条记录
public void testQueryList() throws Exception{
QueryRunner runner = new QueryRunner();
Connection conn = JDBCUtils.getConnByDruid();
String sql = "select id,name,email,birth from customers where id < ?";
BeanListHandler<Customer> handler = new BeanListHandler<>(Customer.class);
List<Customer> list = runner.query(conn, sql, handler, 23);
list.forEach(System.out::println);
JDBCUtils.closeResource(conn, null);
}
自定义ResultSetHandler类
public void testQueryInstance1() throws Exception{
QueryRunner runner = new QueryRunner();
Connection conn = JDBCUtils.getConnByDruid();
String sql = "select id,name,email,birth from customers where id = ?";
ResultSetHandler<Customer> handler = new ResultSetHandler<Customer>() {
@Override
public Customer handle(ResultSet rs) throws SQLException {
...
}
return null;
}
};
Customer customer = runner.query(conn, sql, handler, 23);
System.out.println(customer);
JDBCUtils.closeResource(conn, null);
}
获取单个数值
public void testQueryValue() throws Exception{
QueryRunner runner = new QueryRunner();
Connection conn = JDBCUtils.getConnByDruid();
String sql = "select max(birth) from customers";
ScalarHandler handler = new ScalarHandler();
Date birth = (Date) runner.query(conn, sql, handler);
System.out.println(birth);
JDBCUtils.closeResource(conn, null);
}
参考:JDBC基础学习总结笔记