概述
数据持久化
- 持久化(persistence):把数据保存到可掉电式存储设备中以供之后使用。大多数情况下,特别是企业级应用,数据持久化意味着将内存中的数据保存到硬盘上加以”固化”,而持久化的实现过程大多通过各种
关系数据库
来完成。 - 持久化的主要应用是将内存中的数据存储在关系型数据库中,当然也可以存储在磁盘文件、XML数据文件中。
java中的数据存储技术
- JDBC直接访问数据库
- JDO技术
- 第三方O/R工具,如Hibernate, mybatis 等
什么是JDBC?
- JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口(一组API),定义了用来访问数据库的标准Java类库,(java.sql,javax.sql)使用这个类库可以以一种标准的方法、方便地访问数据库资源
- JDBC为访问不同的数据库提供了一种统一的途径,为开发者屏蔽了一些细节问题。
- 由各种数据库管理系统的开发公司提供JDBC驱动,Java程序员直接使用JDBC连接这些DBMS,大大简化了开发的流程。
JDBC体系结构
- 面向应用的API:Java API,抽象接口,供应用程序开发人员使用(连接数据库,执行SQL语句,获得结果)。
- 面向数据库的API:Java Driver API,供开发商开发数据库驱动程序用。
JDBC API
JDBC API 是一系列的接口,它使得应用程序能够进行数据库联接,执行SQL语句,并且得到返回结果。
JDBC访问数据库的步骤
获取数据库连接
Driver接口
java.sql.Driver 接口是所有 JDBC 驱动程序需要实现的接口。这个接口是提供给数据库厂商使用的,不同数据库厂商提供不同的实现
步骤
- 获取Driver实现类对象
- 提供要连接的数据库
- 提供连接需要的用户名和密码
- 获取连接
下面将通过五种方式逐步进阶
方式一
@Test
public void test1() throws SQLException {
// 获取Driver实现类对象
Driver driver = new com.mysql.jdbc.Driver();
// url:http://localhost:8080/gmall/keyboard.jpg
// jdbc:mysql:协议
// localhost:ip地址
// 3306:默认mysql的端口号
// test:test数据库
String url = "jdbc:mysql://localhost:3306/test";
// 将用户名和密码封装在Properties中
Properties info = new Properties();
info.setProperty("user","root");
info.setProperty("password","073838");
Connection connection = driver.connect(url,info);
System.out.println(connection);
}
运行结果:
com.mysql.jdbc.JDBC4Connection@449b2d27
方式一出现了第三方的API,因此引入方式二进行改进,使其有更好的移植性
方式二
public void test2() throws Exception {
// 1.获取Driver实现类对象:使用反射
Class clazz = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) clazz.newInstance();
// 2.提供要连接的数据库
String url = "jdbc:mysql://localhost:3306/test";
// 3.提供连接需要的用户名和密码
Properties info = new Properties();
info.setProperty("user","root");
info.setProperty("password","073838");
// 4.获取连接
Connection connection = driver.connect(url,info);
System.out.println(connection);
}
方式三:使用DriverManager替换Driver
DriverManager 类是驱动程序管理器类,负责管理驱动程序
public void test3() throws Exception {
// 1.获取Driver实现类对象:使用反射
Class clazz = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) clazz.newInstance();
// 2.提供另外三个连接的基本信息:
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String password = "073838";
// 注册驱动
DriverManager.registerDriver(driver);
// 获取连接
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println(connection);
}
通常不用显式调用 DriverManager 类的 registerDriver() 方法来注册驱动程序类的实
例,因为 Driver 接口的驱动程序类都包含了静态代码块,在这个静态代码块中,会
调用 DriverManager.registerDriver() 方法来注册自身的一个实例,因此对方式三进行改造
方式四:可以只是加载驱动,不用显式的注册驱动
public void test4() throws Exception {
// 1.提供另外三个连接的基本信息:
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String password = "073838";
// 2.加载Driver
Class.forName("com.mysql.jdbc.Driver");
// 3.获取连接
Connection conn = DriverManager.getConnection(url, user, password);
System.out.println(conn);
}
Driver类加载的静态代码块:
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
方式四还暴露了账户名和密码,对次进行最后的改进:将数据库连接需要的4个基本信息声明在配置文件中,通过读取配置文件的方式,获取连接
方式五
配置文件:jdbc.propertoes(注意不要加空格)
user=root
password=073838
url=jdbc:mysql://localhost:3306/test
driver=com.mysql.jdbc.Driver
具体实现:
public void test5() throws Exception {
//1.读取配置文件中的4个基本信息
InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties properties = new Properties();
properties.load(is);
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driverClass = properties.getProperty("driver");
//2.加载驱动
Class.forName("driver");
//3.获取连接
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println(connection);
}
该方式的好处:
1.实现了数据与代码的分离。实现了解耦
2.如果需要修改配置文件信息,可以避免程序重新打包。
使用Statement操作数据库的弊端
获取数据库的连接后,我们可以对数据库进行增删改查操作(CRUD),在 java.sql 包中有 3 个接口分别定义了对数据库的调用的不同方式:
Statement
⇡PreparedStatement
⇡CallableStatement
一般我们不使用Statement来访问数据库,因为会发生SQL注入的问题,下面通过例子来演示SQL注入问题
public void testLogin() {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入用户名:");
String user = scanner.nextLine();
System.out.print("请输入密码:");
String password = scanner.nextLine();
//SELECT user,password FROM user_table WHERE user = '1' or ' AND password = '=1 or '1' = '1'
String sql = "SELECT user,password FROM user_table WHERE user = '"+ user +"' AND password = '"+ password +"'";
User returnUser = get(sql,User.class);
if(returnUser != null){
System.out.println("登录成功");
}else{
System.out.println("用户名不存在或密码错误");
}
}
当我们输入的用户名和密码非法时,可能不会被检测到,但却能对数据库进行访问进行非法操作。
SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令(如:SELECT user, password FROM user_table WHERE user=‘a’ OR 1 = ’ AND password = ’ OR ‘1’ = ‘1’) ,从而利用系统的 SQL 引擎完成恶意行为的做法
对于 Java 而言,要防范 SQL 注入,只要用 PreparedStatement(从Statement扩展而来) 取代 Statement 就可以了
使用PreparedStatement进行CRUD操作
提供整个过程获取数据库连接、关闭资源的工具类
package com.deserts.util;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class JDBCUtils {
/**
* 获取数据库的连接
* @return Connection
* @throws Exception
*/
public static Connection getConnection() throws Exception {
// 1.读取配置文件中的4个基本信息
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
Properties properties = new Properties();
properties.load(is);
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driverClass = properties.getProperty("driver");
// 2.加载驱动
Class.forName(driverClass);
//3.获取连接
Connection connection = DriverManager.getConnection(url, user, password);
return connection;
}
/**
* 关闭资源的操作
* @param connection
* @param ps
*/
public static void closeResource(Connection connection, PreparedStatement ps) {
try {
if(ps != null)
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(connection != null)
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 关闭资源的操作
* @param connection
* @param ps
* @param resultSet
*/
public static void closeResource(Connection connection, PreparedStatement ps, ResultSet resultSet) {
try {
if(ps != null)
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(connection != null)
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(resultSet != null)
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
通用的增删改操作
主要思想:
获取数据库连接→预编译sql语句,获取PrepareStatement的实例ps→根据传入的可变参数填充占位符→调用ps.excute()执行→关闭资源
package com.deserts.crud;
import com.deserts.util.JDBCUtils;
import org.junit.Test;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.text.ParseException;
import java.text.SimpleDateFormat;
public class PreparedStatementUpdateTest {
//封装通用的增删改操作
private void update(String sql, Object ...args) {
Connection connection = null;
PreparedStatement ps = null;
try {
//1.获取数据库连接
connection = JDBCUtils.getConnection();
//2.预编译sql语句,获取PrepareStatement的实例
ps = connection.prepareStatement(sql);
//3.填充占位符
for (int i = 0; i < args.length ; i++) {
ps.setObject(i+1,args[i]);
}
//4.执行
ps.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
//5.关闭资源
JDBCUtils.closeResource(connection, ps);
}
}
//修改
@Test
public void test1(){
String sql = "update `order` set order_name = ? where order_id = ?";
update(sql,"MM",2);
}
//插入
@Test
public void test2() throws ParseException {
String sql = "insert into `order` values(?,?,?)";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
java.util.Date date = sdf.parse("2000-01-01");
update(sql,5,"PP",new Date(date.getTime()));
}
//删除
@Test
public void test3(){
String sql = "delete from `order` where order_id = ?";
update(sql,5);
}
}
对Custmoer表的PreparedStatement查询操作
说明:本过程需要提供Customer对象,用于保存从表中取出的数据
对表中单个对象(一列)的特定查询操作测试
//对单个查询的操作测试
@Test
public void queryTest1(){
Connection connection = null;
PreparedStatement ps = null;
ResultSet resultSet = null;
try {
//1.获取数据库连接
connection = JDBCUtils.getConnection();
String sql = "select id,name,email,birth from customers where id = ?";
//2.预编译sql语句
ps = connection.prepareStatement(sql);
ps.setObject(1,5);
//3.返回结果集
resultSet = ps.executeQuery();
//4.处理结果集
if (resultSet.next()){
//获取当前这条数据的各个字段值
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
String email = resultSet.getString(3);
Date birth = resultSet.getDate(4);
//将数据封装为对象
Customer customer = new Customer(id, name, email, birth);
System.out.println(customer);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭资源
JDBCUtils.closeResource(connection,ps,resultSet);
}
}
查询结果:
对表中单个对象(一列)的通用查询操作测试
主要思想:
获取数据库连接→预编译sql语句,获取PrepareStatement的实例ps→根据传入的可变参数填充占位符→调用ps.excuteQuery(),获取结果集→根据结果集获取结果集的元数据→处理结果集→关闭资源
处理结果集的思路:
由结果集获取列值getObject()→由元数据获取列名getCulumnName()→通过反射给指定对象赋值→返回对象
package com.deserts.crud1;
import com.deserts.bean.Customer;
import com.deserts.util.JDBCUtils;
import org.junit.Test;
import java.lang.reflect.Field;
import java.sql.*;
public class CustomerForQuery {
/**
* 针对Customer表一个对象的通用查询操作
* @param sql
* @param args
* @return
*/
public Customer queryForCustomer(String sql, Object...args){
Connection connection = null;
PreparedStatement ps = null;
ResultSet resultSet = null;
try {
//1.获取数据库连接
connection = JDBCUtils.getConnection();
//2.预编译sql语句
ps = connection.prepareStatement(sql);
//3.填充占位符,返回结果集
for(int i = 0;i < args.length;i++){
ps.setObject(i + 1, args[i]);
}
resultSet = ps.executeQuery();
//4.获取结果集的元数据 :ResultSetMetaData
ResultSetMetaData metaData = resultSet.getMetaData();
//5.通过ResultSetMetaData获取结果集中的列数
int columnCount = metaData.getColumnCount();
//6.处理结果集
if (resultSet.next()){
Customer customer = new Customer();
for (int i = 0; i < columnCount ; i++) {
//获取列值
Object columnValue = resultSet.getObject(i + 1);
//获取每个列的列名
String columnName = metaData.getColumnName(i + 1);
//给customer对象指定的columnName属性,赋值为columnValue:通过反射
Field field = customer.getClass().getDeclaredField(columnName);
field.setAccessible(true);
field.set(customer,columnValue);
}
return customer;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection,ps,resultSet);
}
return null;
}
//测试通用查询操作
@Test
public void queryTest2(){
String sql = "select id,name,email,birth from customers where id = ?";
Customer customer = queryForCustomer(sql,5);
System.out.println(customer);
sql = "select id,name,email from customers where id = ?";
Customer customer1 = queryForCustomer(sql, 10);
System.out.println(customer1);
}
}
查询结果:
对Order表的PreparedStatement查询操作
说明:本过程同样需要提供Order对象
主要思想:
该过程的思想与查询Customer表一致,需要注意的是,Order对象的属性名与order表的列名并不相同,这时需要在查询过程SQL语句中给列名取别名,而获取列的别名用的方法是getColumnTable()(没有别名时返回列名)
package com.deserts.crud1;
import com.deserts.bean.Order;
import com.deserts.util.JDBCUtils;
import org.junit.Test;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
public class OrderForQuery {
private ArrayList<Order> orderQuery(String sql, Object...args){
Connection connection = null;
PreparedStatement ps = null;
ResultSet resultSet = null;
try {
ArrayList<Order> list = new ArrayList<>();
connection = JDBCUtils.getConnection();
ps = connection.prepareStatement(sql);
for (int i = 0; i < args.length ; i++) {
ps.setObject(i+1,args[i]);
}
resultSet = ps.executeQuery();
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
while (resultSet.next()){
Order order = new Order();
for (int i = 0; i < columnCount; i++) {
Object columnValue = resultSet.getObject(i + 1);
String columnLabel = metaData.getColumnLabel(i+1);
Field field = Order.class.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(order, columnValue);
}
list.add(order);
}
return list;
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection,ps,resultSet);
}
return null;
}
@Test
public void orderQueryTest(){
String sql = "select order_id orderId,order_name orderName from `order` where order_id < ?";
ArrayList<Order> orders = orderQuery(sql, 5);
orders.forEach(System.out::println);
}
}
查询结果:
对不同表的通用查询操作
由上面两个张表的查询,我们可以总结出对不同表的通用查询操作,其中查询多条记录只需将处理结果集的if(resultSet.next())改成while(resultSet.next())即可
package com.deserts.crud2;
import com.deserts.bean.Customer;
import com.deserts.bean.Order;
import com.deserts.util.JDBCUtils;
import org.junit.Test;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
public class PreparedStatementQueryTest {
private <T> ArrayList<T> simpleQuery(Class<T> clazz,String sql, Object...args){
Connection connection = null;
PreparedStatement ps = null;
ResultSet resultSet = null;
try {
ArrayList<T> list = new ArrayList<>();
connection = JDBCUtils.getConnection();
ps = connection.prepareStatement(sql);
for (int i = 0; i < args.length ; i++) {
ps.setObject(i+1,args[i]);
}
resultSet = ps.executeQuery();
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
while (resultSet.next()){
T t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
Object columnValue = resultSet.getObject(i + 1);
String columnLabel = metaData.getColumnLabel(i+1);
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, columnValue);
}
list.add(t);
}
return list;
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection,ps,resultSet);
}
return null;
}
@Test
public void queryTest(){
String sql = "select id,name,email from customers where id < ?";
ArrayList<Customer> customers = simpleQuery(Customer.class, sql, 10);
customers.forEach(System.out::println);
System.out.println();
sql = "select order_id orderId,order_name orderName from `order` where order_id < ?";
ArrayList<Order> orders = simpleQuery(Order.class, sql, 5);
orders.forEach(System.out::println);
}
}
查询结果:
查询过程图解
两种思想、两种技术
两种思想:
1.面向接口编程思想
2.ORM编程思想
两种技术:
1.JDBC元数据:ResultSetMetaData
2.通过反射,获取指定的属性,并赋值
Java与SQL数据类型对应表
操作BLOB类型字段
BLOB类型
插入BLOB类型的数据必须使用PreparedStatement,因为BLOB类型的数据无法使用字符串拼接写的。
MySQL中BLOB的四种类型:
注意点:
如果在指定了相关的Blob类型以后,还报错:xxx too large,那么在mysql的安装目录下,找my.ini文件加上如下的配置参数: max_allowed_packet=16M。同时注意:修改了my.ini文件之后,需要重新启动mysql服务。
使用PreparedStatement插入和查询BLOB类型字段
package com.deserts.bolbtest;
import com.deserts.util.JDBCUtils;
import org.junit.Test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
public class Blobtest1 {
//像表中插入含有blob类型字段的记录
@Test
public void test1() throws Exception {
Connection connection = JDBCUtils.getConnection();
String sql = "insert into customers (name,email,birth,photo)values(?,?,?,?)";
PreparedStatement ps = connection.prepareStatement(sql);
ps.setObject(1,"deserts");
ps.setObject(2,"deserts@126.com");
ps.setObject(3,"1986-5-30");
InputStream is = new FileInputStream("E:\\IDEA Projects\\deserts\\deserts.jpg");
ps.setBlob(4,is);
ps.execute();
JDBCUtils.closeResource(connection,ps);
System.out.println("插入成功!");
}
//查询表中含有blob字段的记录
@Test
public void test2(){
Connection connection = null;
PreparedStatement ps = null;
ResultSet resultSet = null;
InputStream is = null;
FileOutputStream fos = null;
try {
connection = JDBCUtils.getConnection();
String sql = "select id,name,email,birth,photo from customers where id = ?";
ps = connection.prepareStatement(sql);
ps.setInt(1,19);
resultSet = ps.executeQuery();
is = null;
fos = null;
if (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
String email = resultSet.getString("email");
Date birth = resultSet.getDate("birth");
Customer customer = new Customer(id,name,email,birth);
System.out.println(customer);
Blob photo = resultSet.getBlob("photo");
is = photo.getBinaryStream();
fos = new FileOutputStream("deserts1.jpg");
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1){
fos.write(buffer,0,len);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection,ps,resultSet);
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
使用PreparedStatement批量插入
批量插入用到的方法
-
addBatch(String):添加需要批量处理的SQL语句或是参数;
-
executeBatch():执行批量处理语句;
-
clearBatch():清空缓存的数据
高效进行批量插入
方式一:使用Statement进行批量插入,效率过低
方式二:使用Preparedstament进行批量插入
@Test
public void test2(){
Connection connection = null;
PreparedStatement ps = null;
try {
long start = System.currentTimeMillis();
connection = JDBCUtils.getConnection();
String sql = "insert into goods(id,name) values(?,?)";
ps = connection.prepareStatement(sql);
for (int i = 1; i <= 20000; i++) {
ps.setInt(1,i);
ps.setString(2,"name_" + i);
ps.execute();
}
long end = System.currentTimeMillis();
System.out.println("花费的时间:" + (end - start));//718571
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection,ps);
}
}
花费时间为:718571毫秒
方式三:使用批量插入相关方法进行优化
@Test
public void test3(){
Connection connection = null;
PreparedStatement ps = null;
try {
long start = System.currentTimeMillis();
connection = JDBCUtils.getConnection();
String sql = "insert into goods(id,name) values(?,?)";
ps = connection.prepareStatement(sql);
for (int i = 1; i <= 20000; i++) {
ps.setInt(1,i);
ps.setString(2,"name_"+i);
ps.addBatch();
if (i % 500 == 0){
ps.executeBatch();
ps.clearBatch();
}
}
long end = System.currentTimeMillis();
System.out.println("花费的时间:" + (end - start));//3260
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection,ps);
}
}
花费的时间:3260毫秒
方式四:设置事务,不自动提交事务
@Test
public void test4(){
Connection connection = null;
PreparedStatement ps = null;
try {
long start = System.currentTimeMillis();
connection = JDBCUtils.getConnection();
connection.setAutoCommit(false);
String sql = "insert into goods(id,name) values(?,?)";
ps = connection.prepareStatement(sql);
for (int i = 1; i <= 20000; i++) {
ps.setInt(1,i);
ps.setString(2,"name_"+i);
ps.addBatch();
if (i % 500 == 0){
ps.executeBatch();
ps.clearBatch();
}
}
connection.commit();
long end = System.currentTimeMillis();
System.out.println("花费的时间:" + (end - start));//2213
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection,ps);
}
}
花费的时间:2213毫秒
综上,我们采用方式四:使用批量提交相关方法,并设置事务不自动提交
java中使用事务
数据自动提交的情况
- DDL操作一旦执行,都会自动提交;set autocommit = false 对DDL操作失效
- DML默认情况下,一旦执行,就会自动提交。我们可以通过set autocommit = false的方式取消DML操作的自动提交。
- 默认在关闭连接时,会自动的提交数据
Java中控制事务用到的方法
- 获取当前连接的隔离级别:connection.getTransactionIsolation()
- 设置数据库的隔离级别:Connection.TRANSACTION_READ_COMMITTED
- 取消自动提交数据: connection.setAutoCommit(false)
考虑事务后的增删改查
通用的增删改操作—version 2.0 (考虑上事务)
public int update(Connection connection, String sql, Object... args){
PreparedStatement ps = null;
try {
ps = connection.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
return ps.executeUpdate();
} catch ( Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(null,ps);
}
return 0;
}
通用的查询操作,用于返回数据表中的一条记录(version 2.0:考虑上事务)
public <T> T getInstance(Connection connection,Class<T> clazz,String sql, Object... args) throws Exception {
PreparedStatement ps = null;
try {
ps = connection.prepareStatement(sql);
ResultSet resultSet = ps.executeQuery();
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
if (resultSet.next()) {
T t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
Object columnValue = resultSet.getObject(i + 1);
String name = metaData.getColumnLabel(i + 1);
Field field = clazz.getDeclaredField(name);
field.setAccessible(true);
field.set(t,columnValue);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(null,ps);
}
return null;
}
DAO及其相关实现类
BaseDAO
package com.deserts.dao;
import com.deserts.util.JDBCUtils;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public abstract class BaseDAO<T> {
private Class<T> clazz = null;
public BaseDAO() {
//获取当前BaseDAO的子类继承的父类中的泛型
Type genericSuperclass = this.getClass().getGenericSuperclass();
ParameterizedType paramType = (ParameterizedType) genericSuperclass;
Type[] typeArguments = paramType.getActualTypeArguments();//获取了父类的泛型参数
clazz = (Class<T>) typeArguments[0];//泛型的第一个参数
}
// 通用的增删改操作---version 2.0 (考虑上事务)
public int update(Connection conn, String sql, Object... args) {// sql中占位符的个数与可变形参的长度相同!
PreparedStatement ps = null;
try {
// 1.预编译sql语句,返回PreparedStatement的实例
ps = conn.prepareStatement(sql);
// 2.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);// 小心参数声明错误!!
}
// 3.执行
return ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 4.资源的关闭
JDBCUtils.closeResource(null, ps);
}
return 0;
}
// 通用的查询操作,用于返回数据表中的一条记录(version 2.0:考虑上事务)
public T getInstance(Connection conn, String sql, Object... args) {
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();
// 获取结果集的元数据 :ResultSetMetaData
ResultSetMetaData rsmd = rs.getMetaData();
// 通过ResultSetMetaData获取结果集中的列数
int columnCount = rsmd.getColumnCount();
if (rs.next()) {
T t = clazz.newInstance();
// 处理结果集一行数据中的每一个列
for (int i = 0; i < columnCount; i++) {
// 获取列值
Object columValue = rs.getObject(i + 1);
// 获取每个列的列名
// String columnName = rsmd.getColumnName(i + 1);
String columnLabel = rsmd.getColumnLabel(i + 1);
// 给t对象指定的columnName属性,赋值为columValue:通过反射
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, columValue);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(null, ps, rs);
}
return null;
}
// 通用的查询操作,用于返回数据表中的多条记录构成的集合(version 2.0:考虑上事务)
public List<T> getForList(Connection conn, String sql, Object... args) {
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();
// 获取结果集的元数据 :ResultSetMetaData
ResultSetMetaData rsmd = rs.getMetaData();
// 通过ResultSetMetaData获取结果集中的列数
int columnCount = rsmd.getColumnCount();
// 创建集合对象
ArrayList<T> list = new ArrayList<T>();
while (rs.next()) {
T t = clazz.newInstance();
// 处理结果集一行数据中的每一个列:给t对象指定的属性赋值
for (int i = 0; i < columnCount; i++) {
// 获取列值
Object columValue = rs.getObject(i + 1);
// 获取每个列的列名
// String columnName = rsmd.getColumnName(i + 1);
String columnLabel = rsmd.getColumnLabel(i + 1);
// 给t对象指定的columnName属性,赋值为columValue:通过反射
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, columValue);
}
list.add(t);
}
return list;
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(null, ps, rs);
}
return null;
}
//用于查询特殊值的通用的方法
public <E> E getValue(Connection conn, String sql, Object... args) {
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()) {
return (E) rs.getObject(1);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(null, ps, rs);
}
return null;
}
}
CustomerDAO
package com.deserts.dao;
import com.deserts.bean.Customer;
import java.sql.Connection;
import java.sql.Date;
import java.util.List;
/**
* @ClassName CustomerDAO
* @Description 此接口用于规范针对于customers表的常用操作
* @Author deserts
* @Date 2020/8/2 16:53
*/
public interface CustomerDAO {
/**
* @Description 将customer对象添加到数据库中
* @Date 2020/8/2 16:59
* [connection, customer]
* @return void
**/
void insert(Connection connection, Customer customer);
/**
* @Description // 针对指定的id,删除表中的一条记录
* @Date 2020/8/2 17:00
* [connection, id]
* @return void
**/
void deleteById(Connection connection,int id);
/**
* @Description //针对内存中的customer对象,去修改数据表中指定的记录
* @Date 2020/8/2 17:01
* [connection, customer]
* @return void
**/
void update(Connection connection,Customer customer);
/**
* @Description //查询表中指定id的customer对象
* @Date 2020/8/2 17:01
* [connection, id]
* @return com.deserts.bean.Customer
**/
Customer getCustomerById(Connection connection,int id);
/**
* @Description //获取表中所有customer对象
* @Date 2020/8/2 17:02
* [connection]
* @return java.util.List<com.deserts.bean.Customer>
**/
List<Customer> getAll(Connection connection);
/**
* @Description //返回表中customer对象的数目
* @Date 2020/8/2 17:03
* [connection]
* @return java.lang.Long
**/
Long getCount(Connection connection);
/**
* @Description //获取最大生日
* @Date 2020/8/2 17:04
* [connection]
* @return java.sql.Date
**/
Date getMaxBirth(Connection connection);
}
CustomerDAOImpl
package com.deserts.dao;
import com.deserts.bean.Customer;
import java.sql.Connection;
import java.sql.Date;
import java.util.List;
/**
* @ClassName CustomerDAOImpl
* @Description CustomerDAO的实现类,用于操作customers表
* @Author deserts
* @Date 2020/8/2 17:05
*/
public class CustomerDAOImpl extends BaseDAO<Customer> implements CustomerDAO{
@Override
public void insert(Connection connection, Customer customer) {
String sql = "insert into customers(name,email,birth) values(?,?,?)";
update(connection,sql,customer.getName(),customer.getEmail(),customer.getBirth());
}
@Override
public void deleteById(Connection connection, int id) {
String sql = "delete from customers where id = ?";
update(connection,sql,id);
}
@Override
public void update(Connection connection, Customer customer) {
String sql = "update customers set name = ?,email = ?, birth = ? where id = ?";
update(connection,sql,customer.getName(),customer.getEmail(),customer.getBirth(),customer.getId());
}
@Override
public Customer getCustomerById(Connection connection, int id) {
String sql = "select id,name,email,birth from customers where id = ?";
return getInstance(connection, sql, id);
}
@Override
public List<Customer> getAll(Connection connection) {
String sql = "select id,name,email,birth from customers";
return getForList(connection,sql);
}
@Override
public Long getCount(Connection connection) {
String sql = "select count(*) from customers";
return getValue(connection,sql);
}
@Override
public Date getMaxBirth(Connection connection) {
String sql = "select max(birth) from customers";
return getValue(connection, sql);
}
}
数据库连接池
druid数据库连接池(重要)
public Connection getConnection() throws Exception {
Properties properties = new Properties();
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
properties.load(is);
DataSource source = DruidDataSourceFactory.createDataSource(properties);
Connection connection = source.getConnection();
return connection;
}
C3P0数据库连接池
public Connection getConnection() throws SQLException {
ComboPooledDataSource cpds = new ComboPooledDataSource("hellc3p0");
Connection conn = cpds.getConnection();
return conn;
}
DBCP数据库连接池
public Connection getConnection() throws Exception {
Properties properties = new Properties();
FileInputStream is = new FileInputStream(new File("src\\dbcp.properties"));
properties.load(is);
DataSource source = BasicDataSourceFactory.createDataSource(properties);
Connection connection = source.getConnection();
return connection;
}
注意点:若要将这三种方式添加到工具类中使用,应将线程池声明到静态方法外,并用静态代码块进行初始化
Apache-DBUtils实现CRUD操作
Apache-DBUtils简介
- commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。
- API介绍:
- org.apache.commons.dbutils.QueryRunner
- org.apache.commons.dbutils.ResultSetHandler
- 工具类:org.apache.commons.dbutils.DbUtils
DBUtils类关闭资源
增删改
以插入为例:
@Test
public void insertTest(){
Connection connection = null;
try {
QueryRunner runner = new QueryRunner();
connection = JDBCUtils.getConnection();
String sql = "insert into customers(name,email,birth)values(?,?,?)";
int update = runner.update(connection, sql, "lala", "lala@126.com", "1995-01-01");
System.out.println("插入了" + update + "条记录!");
} catch (Exception e) {
e.printStackTrace();
} finally {
DbUtils.closeQuietly(connection);
}
}
查询
ResultSetHandler接口及实现类
-
该接口用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式。
-
ResultSetHandler 接口提供了一个单独的方法:Object handle (java.sql.ResultSet .rs)。
-
主要实现类:
-
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:查询单个值对象
-
查询单条记录
@Test
public void queryTest1(){
Connection connection = null;
try {
QueryRunner runner = new QueryRunner();
connection = JDBCUtils.getConnection();
String sql = "select id,name,email,birth from customers where id = ?";
BeanHandler<Customer> handler = new BeanHandler<>(Customer.class);
Customer customer = runner.query(connection, sql, handler, 19);
System.out.println(customer);
} catch (Exception e) {
e.printStackTrace();
} finally {
DbUtils.closeQuietly(connection);
}
}
查询多条记录
@Test
public void queryTest2(){
Connection connection = null;
try {
QueryRunner runner = new QueryRunner();
connection = JDBCUtils.getConnection();
String sql = "select id,name,email,birth from customers where id < ?";
BeanListHandler<Customer> handler = new BeanListHandler<>(Customer.class);
List<Customer> list = runner.query(connection, sql, handler, 10);
list.forEach(System.out::println);
} catch (Exception e) {
e.printStackTrace();
} finally {
DbUtils.closeQuietly(connection);
}
}
查询特殊值
@Test
public void queryTest3(){
Connection conn = null;
try {
QueryRunner runner = new QueryRunner();
conn = JDBCUtils.getConnection();
String sql = "select count(*) from customers";
ScalarHandler handler = new ScalarHandler();
long count = (long)runner.query(conn, sql, handler);
System.out.println("总记录数:"+count);
} catch (Exception e) {
e.printStackTrace();
} finally {
DbUtils.closeQuietly(conn);
}
}
dler, 19);
System.out.println(customer);
} catch (Exception e) {
e.printStackTrace();
} finally {
DbUtils.closeQuietly(connection);
}
}
#### 查询多条记录
```java
@Test
public void queryTest2(){
Connection connection = null;
try {
QueryRunner runner = new QueryRunner();
connection = JDBCUtils.getConnection();
String sql = "select id,name,email,birth from customers where id < ?";
BeanListHandler<Customer> handler = new BeanListHandler<>(Customer.class);
List<Customer> list = runner.query(connection, sql, handler, 10);
list.forEach(System.out::println);
} catch (Exception e) {
e.printStackTrace();
} finally {
DbUtils.closeQuietly(connection);
}
}
查询特殊值
@Test
public void queryTest3(){
Connection conn = null;
try {
QueryRunner runner = new QueryRunner();
conn = JDBCUtils.getConnection();
String sql = "select count(*) from customers";
ScalarHandler handler = new ScalarHandler();
long count = (long)runner.query(conn, sql, handler);
System.out.println("总记录数:"+count);
} catch (Exception e) {
e.printStackTrace();
} finally {
DbUtils.closeQuietly(conn);
}
}