JDBC
JDBC 介绍
JDBC 概念:
JDBC(Java DataBase Connectivity,Java 数据库连接)是一种用于执行 SQL 语句的 Java API,可以为多种关系型数据库提供统一访问,它是由一组用 Java 语言编写的类和接口组成的。
JDBC 本质:
其实就是 Java 官方提供的一套规范(接口),用于帮助开发人员快速实现不同关系型数据库的连接。
JDBC 快速入门
package com.demo;
import java.sql.*;
public class JdbcDemo {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
// 1. 导入jar包
// 2. 注册驱动
// Class.forName("com.mysql.jdbc.Driver"); // Mysql5 驱动
Class.forName("com.mysql.cj.jdbc.Driver"); // Mysql8 驱动
// 3. 获取连接对象
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/world?serverTimezone=UTC", "root", "admin");
// 4. 获取执行对象
Statement statement = connection.createStatement();
// 5. 执行SQL语句,并接收结果
String sql = "select * from city limit 5";
ResultSet resultSet = statement.executeQuery(sql);
// 6. 处理结果
while(resultSet.next()) {
System.out.println(resultSet.getString("Name") + "\t" + resultSet.getString("Population"));
}
// 7. 释放资源
statement.close();
connection.close();
}
}
JDBC API 详解
DriverManager(驱动管理对象)
1)注册驱动(告诉程序该使用哪一个数据库驱动)
- 注册给定的驱动程序:static void registerDriver(Driver driver);
- 写代码时使用:Class.forName("com.mysql.jdbc.Driver");
- 通过查看源码发现:在 com.mysql.jdbc.Driver 类中存在以下静态代码块
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
- 我们不需要通过 DriverManager 调用静态方法 registerDriver,因为只要 Driver 类被使用,则会执行其静态代码块完成注册驱动。
- Mysql5 之后可以省略注册驱动的步骤。因为在驱动 jar 包中,存在一个 javasql.Driver 配置文件,文件中指定了 com.mysqljdbc.Driver。
2)获取数据库连接对象
static Connection getConnection(String url, String user, String password);
- url:指定连接的路径。语法:
jdbc:mysql://ip地址(域名):端口号/库名
- user:数据库用户名
- password:数据库密码
- 返回值 Connection:数据库连接对象
Connection(数据库连接对象)
- 获取执行对象
- 获取普通执行对象:
Statement createStatement();
- 获取预编译执行对象:
PreparedStatement prepareStatement(String sql);
- 获取普通执行对象:
- 管理事务
- 开启事务:setAutoCommit(boolean autoCommit); // 参数为 false,则开启事务
- 提交事务:commit();
- 回滚事务:rollback();
- 释放资源
- 立即释放连接对象:void close();
Statement(SQL 执行对象)
-
执行 DML 语句:
int executeUpdate(String sql);
- 返回值 int:返回影响的行数。
- 参数 sql:可以执行 insert、update、delete 语句
-
执行 DQL 语句:
ResultSet executeQuery(String sql);
- 返回值 ResultSet:封装查询的结果。
- 参数 sql:可以执行select语句。
-
释放资源
- 立即释放执行对象:void close();
ResultSet(结果集对象)
- 判断结果集中是否还有数据:
boolean next();
- 有数据则返回 true,并将索引向下移动一行
- 没有数据则返回 false
- 获取结果集中的数据:
XXX getXxx("列名");
- XXX 代表要获取的某列数据的类型
- 例如:
String getString("name");
、int getInt("age");
- 释放资源
- 立即释放结果集对象:void close();
SQL 注入与预编译
SQL 注入
SQL 注入演示:在登录界面,输入一个错误的用户名或密码,也可以登录成功
SQL 注入的原理:
- 按照正常道理来说,我们在密码处输入的所有内容,都应该作为密码(这个参数)。
- 但是现在 Statement 对象在执行 SQL 语句时,将输入的内容当做查询条件来执行了。
PreparedStatement 预编译
PreparedStatement 即预编译 SQL 语句的执行对象,是 SQL 注入的防御手段之一。其原理是:在执行 SQL 语句之前,将 SQL 语句进行提前编译,在明确 SQL 语句的格式(执行计划)后,就不会改变了。因此剩余的内容都会认为是参数。
参数使用?
作为占位符:
- 为参数赋值的方法:
setXxx(参数 1, 参数 2);
- 参数 1:? 的位置编号(编号从 1 开始)
- 参数 2:? 的实际参数
- 执行 SQL 语句的方法
- 执行 insert、update、delete 语句:
int executeUpdate();
- 执行 select 语句:
ResultSet executeQuery();
- 执行 insert、update、delete 语句:
JDBC 案例:student 表 CRUD
表数据
-- 创建student表
CREATE TABLE student(
sid INT PRIMARY KEY AUTO_INCREMENT, -- 学生id
NAME VARCHAR(20), -- 学生姓名
age INT, -- 学生年龄
birthday DATE -- 学生生日
);
-- 添加数据
INSERT INTO student VALUES (NULL,'张三',23,'1999-09-23'), (NULL,'李四',24,'1998-08-10'), (NULL,'王五',25,'1996-06-06'), (NULL,'赵六',26,'1994-10-20');
JDBC 配置信息
config.properties:
driverClass=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/world?serverTimezone=UTC
username=root
password=admin
domain 实体类
Student 实体类:
package com.bean;
import java.util.Date;
public class Student {
private Integer sid;
private String name;
private Integer age;
private Date birthday;
public Student() {
}
public Student(Integer sid, String name, Integer age, Date birthday) {
this.sid = sid;
this.name = name;
this.age = age;
this.birthday = birthday;
}
public Integer getSid() {
return sid;
}
public void setSid(Integer sid) {
this.sid = sid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "student{" +
"sid=" + sid +
", name='" + name + '\'' +
", age=" + age +
", birthday=" + birthday +
'}';
}
}
JDBC 工具类
package com.utils;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
// JDBC 工具类
public class JdbcUtil {
// 私有构造方法
private JdbcUtil(){}
// 声明所需要的配置变量
private static String driverClass;
private static String url;
private static String username;
private static String password;
private static Connection connection;
// 静态代码块:读取配置文件的信息为变量赋值,注册驱动
static {
try{
// 读取配置文件
InputStream resourceAsStream = JdbcUtil.class.getClassLoader().getResourceAsStream("config.properties");
Properties properties = new Properties();
properties.load(resourceAsStream);
// 赋值
driverClass = properties.getProperty("driverClass");
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");
// 注册驱动
Class.forName(driverClass);
} catch (Exception e) {
e.printStackTrace();
}
}
// 获取数据库连接对象
public static Connection getConnection() {
try {
connection = DriverManager.getConnection(url, username, password);
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
// 释放资源
public static void close(Connection connection, Statement statement, ResultSet resultSet){
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
// 释放资源
public static void close(Connection connection, Statement statement) {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
Dao 层
StudentDao.java:
package com.dao;
import com.bean.Student;
import java.util.ArrayList;
// Dao层接口
public interface StudentDao {
// 查询所有学生信息
public abstract ArrayList<Student> findAll();
// 根据id条件查询
public abstract Student findById(Integer id);
// 新增学生信息
public abstract int insert(Student student);
// 修改学生信息
public abstract int update(Student student);
// 根据id删除学生信息
public abstract int delete(Integer id);
}
StudentDaoImpl.java:
package com.dao;
import com.bean.Student;
import com.utils.JdbcUtil;
import java.sql.*;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
public class StudentDaoImpl implements StudentDao{
// 查询所有学生信息
@Override
public ArrayList<Student> findAll() {
ArrayList<Student> studentList = new ArrayList<>();
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
connection = JdbcUtil.getConnection();
String sql = "select * from student";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
resultSet = preparedStatement.executeQuery();
// 处理结果集
while (resultSet.next()) {
Integer sid = resultSet.getInt("sid");
String name = resultSet.getString("name");
Integer age = resultSet.getInt("age");
Date birthday = resultSet.getDate("birthday");
// 封装Student对象
Student student = new Student(sid, name, age, birthday);
// 将student对象保存到集合中
studentList.add(student);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源
JdbcUtil.close(connection, statement, resultSet);
}
// 返回集合对象
return studentList;
}
// 条件查询,根据id查询学生信息
@Override
public Student findById(Integer id) {
Student student = new Student();
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
connection = JdbcUtil.getConnection();
String sql = "select * from student where sid=?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1, id);
resultSet = preparedStatement.executeQuery();
// 处理结果集
while (resultSet.next()) {
Integer sid = resultSet.getInt("sid");
String name = resultSet.getString("name");
Integer age = resultSet.getInt("age");
Date birthday = resultSet.getDate("birthday");
// 封装Student对象
student.setSid(sid);
student.setName(name);
student.setAge(age);
student.setBirthday(birthday);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtil.close(connection, statement, resultSet);
}
return student;
}
// 添加学生信息
@Override
public int insert(Student student) {
Connection connection = null;
Statement statement = null;
int result = 0;
try {
connection = JdbcUtil.getConnection();
Date raw_birthday = student.getBirthday();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
String birthday = simpleDateFormat.format(raw_birthday);
String sql = "insert into student (sid, name, age, birthday) values (?, ?, ?, ?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1, student.getSid());
preparedStatement.setString(2, student.getName());
preparedStatement.setInt(3, student.getAge());
preparedStatement.setDate(4, (java.sql.Date) student.getBirthday());
result = preparedStatement.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtil.close(connection, statement);
}
return result;
}
// 修改学生信息
@Override
public int update(Student student) {
Connection connection = null;
Statement statement = null;
int result = 0;
try {
connection = JdbcUtil.getConnection();
Date raw_birthday = student.getBirthday();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
String birthday = simpleDateFormat.format(raw_birthday);
String sql = "UPDATE student SET name=?, age=?, birthday=? WHERE sid=?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, student.getName());
preparedStatement.setInt(2, student.getAge());
preparedStatement.setDate(3, (java.sql.Date) student.getBirthday());
result = preparedStatement.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtil.close(connection, statement);
}
return result;
}
@Override
public int delete(Integer id) {
Connection connection = null;
Statement statement = null;
int result = 0;
try {
connection = JdbcUtil.getConnection();
String sql = "delete from student where sid=?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1, id);
result = preparedStatement.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtil.close(connection, statement);
}
return result;
}
}
Service 层
StudentService.java:
package com.service;
import com.bean.Student;
import java.util.ArrayList;
// service 层接口
public interface StudentService {
//查询所有学生信息
public abstract ArrayList<Student> findAll();
//条件查询,根据id获取学生信息
public abstract Student findById(Integer id);
//新增学生信息
public abstract int insert(Student student);
//修改学生信息
public abstract int update(Student student);
//删除学生信息
public abstract int delete(Integer id);
}
StudentServiceImpl.java:
package com.service;
import com.bean.Student;
import com.dao.StudentDao;
import com.dao.StudentDaoImpl;
import java.util.ArrayList;
public class StudentServiceImpl implements StudentService {
private StudentDao dao = new StudentDaoImpl();
// 查询所有学生信息
@Override
public ArrayList<Student> findAll() {
return dao.findAll();
}
// 根据id查询指定学生信息
@Override
public Student findById(Integer id) {
return dao.findById(id);
}
// 添加学生信息
@Override
public int insert(Student student) {
return dao.insert(student);
}
// 修改学生信息
@Override
public int update(Student student) {
return dao.update(student);
}
// 删除学生信息
@Override
public int delete(Integer id) {
return dao.delete(id);
}
}
Controller 层
StudentController.java:
package com.controller;
import com.bean.Student;
import com.service.StudentService;
import com.service.StudentServiceImpl;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Date;
public class StudentController {
private StudentService studentService = new StudentServiceImpl();
// 查询所有学生信息
@Test
public void findAll() {
ArrayList<Student> studentList = studentService.findAll();
for (Student student: studentList) {
System.out.println(student);
}
}
// 根据id查询指定学生信息
@Test
public void findById() {
Student student = studentService.findById(3);
System.out.println("查询成功: "+student);
}
// 添加学生信息
@Test
public void insert() {
Student student = new Student(5, "陈七", 19, new Date());
int result = studentService.insert(student);
if (result != 0) {
System.out.println("学生信息添加成功!");
} else {
System.out.println("学生信息添加失败!");
}
}
// 修改学生信息
@Test
public void update() {
Student student = studentService.findById(1);
student.setName("xiaoji");
int result = studentService.update(student);
if (result != 0) {
System.out.println("学生信息修改成功!");
} else {
System.out.println("学生信息修改失败!");
}
}
// 删除学生信息
@Test
public void delete() {
int result = studentService.delete(1);
if (result != 0) {
System.out.println("学生信息删除成功!");
} else {
System.out.println("学生信息删除失败!");
}
}
}
事务管理
事务一般在 service 层控制管理,因为事务一般与业务耦合,而不是与通用的 dao 层耦合。
- Service 接口:
import java.util.List;
public interface UserService {
/**
* 批量添加
* @param users
*/
void batchAdd(List<User> users);
}
- ServiceImpl 实现类:
@Override
public void batchAdd(List<User> users) {
// 获取数据库连接
Connection connection = JDBCUtils.getConnection();
try {
// 开启事务
connection.setAutoCommit(false);
for (User user : users) {
// 1.创建ID,并把UUID中的-替换
String uid = UUID.randomUUID().toString().replace("-", "").toUpperCase();
// 2.给user的uid赋值
user.setUid(uid);
// 3.生成员工编号
user.setUcode(uid);
// 手动模拟异常
//int n = 1 / 0;
// 4.保存
userDao.save(connection,user);
}
// 提交事务
connection.commit();
}catch (Exception e){
try {
// 若遇到异常,回滚事务
connection.rollback();
}catch (Exception ex){
ex.printStackTrace();
}
e.printStackTrace();
} finally {
JDBCUtils.close(connection,null,null);
}
}
连接池
连接池概述
数据库连接背景
数据库连接是一种关键的、有限的、昂贵的资源,这一点在多用户的网页应用程序中体现得尤为突出。对数据库连接的管理能显著影响到整个应用程序的伸缩性和健壮性,影响到程序的性能指标。
数据库连接池正是针对这种背景提出来的。
数据库连接池
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。这项技术能解决建立数据库连接耗费资源和时间的问题,明显提高对数据库操作的性能。
数据库连接池原理
开源连接池:C3P0
C3P0 是一个开源的 JDBC 连接池,使用它的开源项目有 Hibernate、Spring 等。
使用步骤:
- 导入 jar 包
- 导入配置文件(c3p0-config.xml,文件名不可改)到 src 目录下
- 创建 c3p0 连接池对象
- 获取数据库连接进行使用
使用示例:
- c3p0-config.xml:
<c3p0-config>
<!-- 使用默认的配置读取连接池对象 -->
<default-config>
<!-- 连接参数 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/world</property>
<property name="user">root</property>
<property name="password">admin</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/world</property>
<property name="user">root</property>
<property name="password">admin</property>
<!-- 连接池参数 -->
<property name="initialPoolSize">5</property>
<property name="maxPoolSize">8</property>
<property name="checkoutTimeout">1000</property>
</named-config>
</c3p0-config>
- 测试类:
import com.mchange.v2.c3p0.ComboPooledDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class C3P0Test {
public static void main(String[] args) throws SQLException {
// 创建c3p0连接池对象
DataSource dataSource = new ComboPooledDataSource();
// 获取数据库连接进行使用
Connection con = dataSource.getConnection();
// 查询全部学生信息
String sql = "SELECT * FROM student";
PreparedStatement pst = con.prepareStatement(sql);
ResultSet rs = pst.executeQuery();
while(rs.next()) {
System.out.println(rs.getInt("sid") + "\t" + rs.getString("name") + "\t" + rs.getInt("age") + "\t" + rs.getDate("birthday"));
}
// 释放资源
rs.close();
pst.close();
con.close(); // 将连接对象归还池中
}
}
* **优化:抽取工具类**
~~~java
package com.itheima.util;
import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.sql.DataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class C3P0Util {
// 得到一个数据源
private static DataSource dataSource = new ComboPooledDataSource();
public static DataSource getDataSource() {
return dataSource;
}
//从数据源中得到一个连接对象
public static Connection getConnection(){
try {
return dataSource.getConnection();
} catch (SQLException e) {
throw new RuntimeException("服务器错误");
}
}
public static void close(Connection conn, Statement stmt, ResultSet rs){
// 关闭资源
if(rs!=null){
try {
rs.close();
} catch (Exception e) {
e.printStackTrace();
}
rs = null;
}
if(stmt!=null){
try {
stmt.close();
} catch (Exception e) {
e.printStackTrace();
}
stmt = null;
}
if(conn!=null){
try {
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
conn = null;
}
}
}
开源连接池:Druid
数据库连接池有很多选择,C3P0、DHCP 等,阿里巴巴开源的 druid 作为一名后起之秀,凭借其出色的性能,也逐渐印入了大家的眼帘。
使用步骤:
- 导入 jar 包
- 通过 Properties 集合加载配置文件
- 通过 Druid 连接池工厂类获取数据库连接池对象
- 获取数据库连接,进行使用
示例:
- druid.properties:
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/world
username=root
password=itheima
# 初始化连接数量
initialSize=5
# 最大连接数量
maxActive=10
# 超时时间
maxWait=3000
- 测试类:
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;
public class DruidTest {
public static void main(String[] args) throws Exception {
// 通过Properties集合加载配置文件
InputStream is = DruidTest.class.getClassLoader().getResourceAsStream("druid.properties");
Properties prop = new Properties();
prop.load(is);
// 通过Druid连接池工厂类获取数据库连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
// 获取数据库连接,进行使用
Connection con = dataSource.getConnection();
// 查询全部学生信息
String sql = "SELECT * FROM student";
PreparedStatement pst = con.prepareStatement(sql);
ResultSet rs = pst.executeQuery();
while(rs.next()) {
System.out.println(rs.getInt("sid") + "\t" + rs.getString("name") + "\t" + rs.getInt("age") + "\t" + rs.getDate("birthday"));
}
// 释放资源
rs.close();
pst.close();
con.close(); // 将连接对象归还池中
}
}
- 优化:抽取工具类
public class DataSourceUtils {
// 1.私有构造方法
private DataSourceUtils(){}
// 2.定义DataSource数据源变量
private static DataSource dataSource;
// 3.提供静态代码块,完成配置文件的加载和获取连接池对象
static {
try{
// 加载配置文件
InputStream is = DruidDemo1.class.getClassLoader().getResourceAsStream("druid.properties");
Properties prop = new Properties();
prop.load(is);
// 获取数据库连接池对象
dataSource = DruidDataSourceFactory.createDataSource(prop);
} catch(Exception e) {
e.printStackTrace();
}
}
// 4.提供获取数据库连接的方法
public static Connection getConnection() {
Connection con = null;
try {
con = dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return con;
}
// 5.提供获取数据库连接池的方法
public static DataSource getDataSource() {
return dataSource;
}
// 6.提供DQL释放资源的方法
public static void close(Connection con, Statement stat, ResultSet rs) {
if(con != null) {
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stat != null) {
try {
stat.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
// 提供DML释放资源的方法
public static void close(Connection con, Statement stat) {
close(con, stat, null);
}
}
DBUtils
DBUtils 介绍
什么是 DBUtils ?其作用?
DBUtils 是 Apache 开源的 Java 编程中的数据库操作实用工具,小巧简单实用。
DBUtils 封装了对 JDBC 的操作,简化了 JDBC 操作,可以少写代码。
- 对于数据表的读操作,他可以把结果转换成 List、Array、Set 等 Java 集合,便于程序员操作。
- 对于数据表的写操作,也变得很简单(只需写 SQL 语句)。
- 可以使用数据源,使用 JNDI,数据库连接池等技术来优化性能--重用已经构建好的数据库连接对象。
DBUtils 的三个核心对象:
QueryRunner 类
:提供对 SQL 语句操作的 API,它主要有三个方法:- `query():用于执行 select
update()
:用于执行 insert、update、deletebatch()
:批处理
ResultSetHandler 接口
:用于定义 select 操作后怎样封装结果集。DbUtils 类
:一个工具类,定义了关闭资源与事务处理的方法。
使用案例
DBUtils 使用步骤:
- 导入 jar 包
- c3p0-0.9.1.2.jar(其他连接对象均可)
- commons-logging-1.1.1.jar
- commons-beanutils-1.8.3.jar
- commons-dbutils-1.4.jar
- 创建 QueryRunner 对象
- 使用 query 方法执行 select 语句
- 使用 ResultSetHandler 封装结果集
- 使用 DbUtils 类释放资源
QueryRunner 类(执行对象)
构造函数:
new QueryRunner();
- 其事务可以手动控制。
- 此对象调用的方法(如 query、update、batrch)参数中要有 Connection 对象。
new QueryRunner(DataSource ds);
- 其事务是自动控制的(一个 SQL 一个事务)。
- 此对象调用的方法(如 query、update、batrch)参数中无需 Connection 对象。
示例:
- C3P0Util.java
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.sql.DataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class C3P0Util {
// 得到一个数据源
private static DataSource dataSource = new ComboPooledDataSource();
public static DataSource getDataSource() {
return dataSource;
}
//从数据源中得到一个连接对象
public static Connection getConnection(){
try {
return dataSource.getConnection();
} catch (SQLException e) {
throw new RuntimeException("服务器错误");
}
}
public static void close(Connection conn, Statement stmt, ResultSet rs){
// 关闭资源
if(rs!=null){
try {
rs.close();
} catch (Exception e) {
e.printStackTrace();
}
rs = null;
}
if(stmt!=null){
try {
stmt.close();
} catch (Exception e) {
e.printStackTrace();
}
stmt = null;
}
if(conn!=null){
try {
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
conn = null;
}
}
}
- DBUtil
import com.bean.Student;
import org.apache.commons.dbutils.QueryRunner;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.commons.dbutils.ResultSetHandler;
import org.junit.jupiter.api.Test;
import org.apache.commons.dbutils.handlers.BeanListHandler;
public class DBUtil {
@Test
public void testDQL1() throws SQLException{
//创建一个QueryRunner对象
QueryRunner qr = new QueryRunner(C3P0Util.getDataSource());
//执行查询语句,并返回结果
List<Student> list = qr.query("select * from student where sid=? and name=?", new BeanListHandler<Student>(Student.class), 3, "王五");
for (Student student : list) {
System.out.println(student);
}
}
@Test
public void testDQL2() throws SQLException{
// 创建一个QueryRunner对象
QueryRunner qr = new QueryRunner(C3P0Util.getDataSource());
List<Student> list = qr.query("select * from student", new ResultSetHandler<List<Student>>(){
// 当query方法执行完select语句,就会将结果集以参数的形式传递进来
public List<Student> handle(ResultSet rs) throws SQLException {
List<Student> list = new ArrayList<Student>();
while(rs.next()){
Student student = new Student();
student.setSid(rs.getInt(1));
student.setName(rs.getString(2));
student.setAge(rs.getInt(3));
student.setBirthday(rs.getDate(4));
list.add(student);
}
return list;
}
});
for (Student student : list) {
System.out.println(student);
}
}
@Test
public void testDML() throws SQLException{
//创建一个QueryRunner对象
QueryRunner qr = new QueryRunner(C3P0Util.getDataSource());
// 返回影响行数
qr.update("insert into student (sid,name,age,birthday) values(?,?,?,?)", "6", "王八", "4", new Date());
}
@Test
public void testBatchDQL() throws SQLException{
//创建一个QueryRunner对象
QueryRunner qr = new QueryRunner(C3P0Util.getDataSource());
Object[][] params = new Object[10][]; // 高维代表执行次数;低维代表
for (int i=0; i<10; i++) {
params[i] = new Object[]{10+i, "菜"+i, 10+i, new Date()};
}
// 返回影响行数
qr.batch("insert into student (sid,name,age,birthday) values(?,?,?,?)", params);
}
}
ResultSetHandler 接口(结果集对象)
ResultSetHandler 下的所有结果处理器:
对象名 | 说明 |
---|---|
ArrayHandler | 适合取 1 条记录。 把该条记录的每列值封装到一个 Object[] 中 |
ArrayListHandler | 适合取多条记录。 把每条记录的每列值封装到一个 Object[] 中,再把数组封装到一个 List 中 |
ColumnListHandler | 取某一列的数据。 把该条记录的每列值封装到 List 中 |
KeyedHandler | 取多条记录。 每一条记录封装到一个 Map 中,再把这个 Map 封装到另外一个 Map 中,key 为指定的字段值 |
MapHandler | 适合取1条记录。 把当前记录的列名和列值放到一个 Map 中 |
MapListHandler | 适合取多条记录。 把每条记录封装到一个 Map 中,再把 Map 封装到 List 中 |
ScalarHandler | 适合取单行单列数据 |
BeanHandler | 取第一行数据 |
BeanListHandler | 将每个数据封装到 List 集合中 |
示例:
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.ArrayHandler;
import org.apache.commons.dbutils.handlers.ArrayListHandler;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ColumnListHandler;
import org.apache.commons.dbutils.handlers.KeyedHandler;
import org.apache.commons.dbutils.handlers.MapHandler;
import org.apache.commons.dbutils.handlers.MapListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import org.junit.Test;
public class ResultSetHandler {
//ArrayHandler:适合取1条记录。把该条记录的每列值封装到一个数组中Object[]
@Test
public void test1() throws SQLException {
QueryRunner qr = new QueryRunner(c3p0.getDataSource());
Object[] arr = qr.query("select * from users where id =?",new ArrayHandler(),5);
for (Object o : arr) {
System.out.println(o);
}
}
//ArrayListHandler:适合取多条记录。把每条记录的每列值封装到一个数组中Object[],把数组封装到一个List中
@Test
public void test2() throws SQLException {
QueryRunner qr = new QueryRunner(c3p0.getDataSource());
List<Object[]> list = qr.query("select * from users",new ArrayListHandler());
for (Object[] o : list) {
for(Object os : o){
System.out.println(os);
}
System.out.println("---------");
}
}
//ColumnListHandler:取某一列的数据,封装到List中。
@Test
public void test3() throws SQLException {
QueryRunner qr = new QueryRunner(c3p0.getDataSource());
//参数的列数是指select语句中列的数
List<Object> list = qr.query("select * from users",new ColumnListHandler(3));
for (Object o : list) {
System.out.println(o);
}
}
//KeyedHandler:取多条记录,每一条记录封装到一个Map中,再把这个Map封装到另外一个Map中,Key为指定的字段值。
@Test
public void test4() throws SQLException {
QueryRunner qr = new QueryRunner(c3p0.getDataSource());
//参数指大Map的Key,是表中的某列数据,若不重复,则作为输出的记录数量。
//小Map中的Key是列名
Map<Object,Map<String,Object>> map = qr.query("select * from users",new KeyedHandler(1));
for (Map.Entry<Object, Map<String, Object>> m : map.entrySet()) {
System.out.println(m.getKey());
for(Map.Entry<String, Object> mm : m.getValue().entrySet()){
System.out.println(mm.getKey()+":"+mm.getValue());
}
System.out.println("---------");
}
}
//MapHandler:适合取1条记录。把当前记录的列名和列值放到一个Map中
@Test
public void test5() throws SQLException {
QueryRunner qr = new QueryRunner(c3p0.getDataSource());
Map<String, Object> map = qr.query("select * from users where id=?",new MapHandler(),6);
for (Map.Entry<String, Object> m : map.entrySet()) {
System.out.println(m.getKey()+":"+m.getValue());
}
System.out.println("---------");
}
//MapListHandler:适合取多条记录。把每条记录封装到一个Map中,再把Map封装到List中
@Test
public void test6() throws SQLException {
QueryRunner qr = new QueryRunner(c3p0.getDataSource());
List<Map<String, Object>> list = qr.query("select * from users",new MapListHandler());
for(Map<String, Object> map : list){
for (Map.Entry<String, Object> m : map.entrySet()) {
System.out.println(m.getKey()+":"+m.getValue());
}
System.out.println("---------");
}
}
//ScalarHandler:适合取单行单列数据
@Test
public void test7() throws SQLException {
QueryRunner qr = new QueryRunner(c3p0.getDataSource());
Object o = qr.query("select username from users",new ScalarHandler(1)); //username列第1个
Object o2 = qr.query("select * from users",new ScalarHandler(2)); //第1行第2列
Object o3 = qr.query("select count(*) from users",new ScalarHandler());
System.out.println(o3);
System.out.println(o.getClass().getName());//查看返回的Object变量是什么类型
}
//BeanHandler:取第一行数据
@Test
public void test8() throws SQLException {
QueryRunner qr = new QueryRunner(c3p0.getDataSource());
User u = qr.query("select * from users",new BeanHandler<User>(User.class));
System.out.println(u);
}
//BeanListHandler:将每个数据封装到List集合中
@Test
public void test9() throws SQLException {
QueryRunner qr = new QueryRunner(c3p0.getDataSource());
List<User> list = qr.query("select * from users where id=?", new BeanListHandler<User>(User.class),2);
//list若取不到值则返回0,不会有空指针异常的问题
for(User user:list){
System.out.println(user);
}
}
}
ThreadLocal(当前线程对象)
作用:调用该类的 get 方法,永远返回当前线程放入的数据(线程局部变量)。
// 模拟 ThreadLocal 的设计,明白其作用
public class ThreadLocal {
private Map<Runnable, Object> container = new HashMap<Runnable, Object>();
public void set(Object value){
container.put(Thread.currentThread(), value); // 用当前线程作为key
}
public Object get(){
return container.get(Thread.currentThread());
}
public void remove(){
container.remove(Thread.currentThread());
}
}
案例:
- ThreadLocal 工具类
import java.sql.Connection;
import java.sql.SQLException;
public class ManageThreadLocal {
private static ThreadLocal<Connection> t1 = new ThreadLocal<Connection>();
// 得到当前线程中的一个连接
public static Connection getConnection(){
Connection conn = t1.get(); // 从当前线程取出一个连接
if(conn==null){
conn = C3P0Util.getConnection(); // 从池中取出一个
t1.set(conn); // 把conn对象放入到当前线程对象中
}
return conn;
}
// 开始事务
public static void startTransaction(){
try {
getConnection().setAutoCommit(false); // 从当前线程对象中取出的连接,并开始事务
} catch (SQLException e) {
e.printStackTrace();
}
}
// 提交事务
public static void commit(){
try {
getConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
// 回滚事务
public static void rollback(){
try {
getConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
public static void close(){
try {
getConnection().close(); // 把连接放回池中
t1.remove(); // 把当前线程对象中的conn移除
} catch (SQLException e) {
e.printStackTrace();
}
}
}
- AccountDaoImpl
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import com.dao.AccountDao;
import com.domain.Account;
import com.util.C3P0Util;
import com.util.ManagerThreadLocal;
public class AccountDaoImpl implements AccountDao {
public void updateAccount(String fromname, String toname, double money) throws Exception {
// 创建一个QueryRunner对象
QueryRunner qr = new QueryRunner(C3P0Util.getDataSource());
qr.update("update account set money=money-? where name=?",money,fromname);
qr.update("update account set money=money+? where name=?",money,toname);
}
public void updateAccout(Account account) throws Exception {
QueryRunner qr = new QueryRunner();
return qr.update(ManagerThreadLocal.getConnection(),"update account set money=? where name=?",account.getMoney(),account.getName());
}
public Account findAccountByName(String name) throws Exception {
QueryRunner qr = new QueryRunner();
return qr.query(ManagerThreadLocal.getConnection(),"select * from account where name=?", new BeanHandler<Account>(Account.class),name);
}
}
- AccountServiceImpl
import java.sql.Connection;
import java.sql.SQLException;
import com.dao.AccountDao;
import com.dao.impl.AccountDaoImpl;
import com.domain.Account;
import com.service.AccountService;
import com.util.C3P0Util;
import com.util.ManagerThreadLocal;
public class AccountServiceImpl implements AccountService {
public void transfer(String fromname, String toname, double money) {
// ad.updateAccount(fromname, toname, money);
AccountDao ad = new AccountDaoImpl();
try {
ManagerThreadLocal.startTransacation(); // begin
// 分别得到转出和转入账户对象
Account fromAccount = ad.findAccountByName(fromname);
Account toAccount = ad.findAccountByName(toname);
// 修改账户各自的金额
fromAccount.setMoney(fromAccount.getMoney()-money);
toAccount.setMoney(toAccount.getMoney()+money);
//完成转账操作
ad.updateAccout(fromAccount);
// int i = 10/0;
ad.updateAccout(toAccount);
ManagerThreadLocal.commit(); // 提交事务
} catch (Exception e) {
try {
ManagerThreadLocal.rollback(); // 回滚事务
} catch (Exception e1) {
e1.printStackTrace();
}
}finally{
try {
ManagerThreadLocal.close();
} catch (Exception e) {
e.printStackTrace();
} // 关闭
}
}
}