JDBC(2024)
以前的JDBC笔记:点此跳转。
基础实现
查询
// 1.注册驱动(可以省略)
// Class.forName("com.mysql.cj.jdbc.Driver");
// DriverManager.registerDriver(new Driver());
// 2.获取连接对象
String url = "jdbc:mysql:///atguigu";
String username = "root";
String password = "atguigu";
Connection connection = DriverManager.getConnection(url, username, password);
// 3.预编译SQL语句获得PreparedStatement对象
PreparedStatement preparedStatement = connection.prepareStatement("SELECT emp_id,emp_name,emp_salary,emp_age FROM t_emp WHERE emp_id = ?");
//4.为占位符赋值,然后执行,并接收结果
preparedStatement.setInt(1,5);
ResultSet resultSet = preparedStatement.executeQuery();
// 5.处理结果
while(resultSet.next()){
int empId = resultSet.getInt("emp_id");
String empName = resultSet.getString("emp_name");
double empSalary = resultSet.getDouble("emp_salary");
int empAge = resultSet.getInt("emp_age");
System.out.println(empId+"\t"+empName+"\t"+empSalary+"\t"+empAge);
}
// 6.资源释放
resultSet.close();
preparedStatement.close();
connection.close();
增删改
主键回显:返回新增行的主键,无需再次查询获得。
// 获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "atguigu");
// 预编译SQL语句,告知preparedStatement,返回新增数据的主键列的值
String sql = "INSERT INTO t_emp(emp_name,emp_salary,emp_age) VALUES (?,?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
// 创建对象,将对象的属性值,填充在?占位符上 (ORM)
Employee employee = new Employee(null, "jack", 123.45, 29);
preparedStatement.setString(1, employee.getEmpName());
preparedStatement.setDouble(2, employee.getEmpSalary());
preparedStatement.setInt(3, employee.getEmpAge());
// 执行SQL,并获取返回的结果
int result = preparedStatement.executeUpdate();
ResultSet resultSet = null;
// 处理结果
if(result > 0){
System.out.println("成功!");
//获取当前新增数据的主键列,回显到Java中employee对象的empId属性上。
//返回的主键值,是一个单行单列的结果存储在ResultSet里
resultSet = preparedStatement.getGeneratedKeys();
if(resultSet.next()){
int empId = resultSet.getInt(1);
employee.setEmpId(empId);
}
System.out.println(employee);
}else{
System.out.println("失败!");
}
// 释放资源
if(resultSet!=null){
resultSet.close();
}
preparedStatement.close();
connection.close();
连接池
Druid
需要导入druid-x.x.x.jar
包。
# properties文件
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/atguigu
username=root
password=atguigu
initialSize=10
maxActive=20
//1.创建Properties集合,用于存储外部配置文件的key和value值。
Properties properties = new Properties();
//2.读取外部配置文件,获取输入流,加载到Properties集合里。
InputStream inputStream = DruidTest.class.getClassLoader().getResourceAsStream("db.properties");
properties.load(inputStream);
//3.基于Properties集合构建DruidDataSource连接池
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
//4.通过连接池获取连接对象
Connection connection = dataSource.getConnection();
System.out.println(connection);
//5.开发CRUD
//6.回收连接
connection.close();
Hikari
需要导入HikariCP-x.x.x.jar
和slf4j-api-x.x.x.jar
包。
# properties文件
driverClassName=com.mysql.cj.jdbc.Driver
jdbcUrl=jdbc:mysql://localhost:3306/atguigu
username=root
password=atguigu
minimumIdle=10
maximumPoolSize=20
//1.创建Properties集合,用于存储外部配置文件的key和value值。
Properties properties = new Properties();
//2.读取外部配置文件,获取输入流,加载到Properties集合里。
InputStream inputStream = HikariTest.class.getClassLoader().getResourceAsStream("hikari.properties");
properties.load(inputStream);
//3.创建HikariConfig连接池配置对象,将Properties集合传进去。
HikariConfig hikariConfig = new HikariConfig(properties);
//4.基于HikariConfig连接池配置对象,构建HikariDataSource
HikariDataSource hikariDataSource = new HikariDataSource(hikariConfig);
//5.获取连接
Connection connection = hikariDataSource.getConnection();
System.out.println(connection);
//6.回收连接
connection.close();
封装工具类
/**
* JDBC工具类(V2.0):
* 1、维护一个连接池对象、维护了一个线程绑定变量的ThreadLocal对象
* 2、对外提供在ThreadLocal中获取连接的方法
* 3、对外提供回收连接的方法,回收过程中,将要回收的连接从ThreadLocal中移除!
* 注意:工具类仅对外提供共性的功能代码,所以方法均为静态方法!
* 注意:使用ThreadLocal就是为了一个线程在多次数据库操作过程中,使用的是同一个连接!
*/
public class JDBCUtil {
// 创建连接池引用,因为要提供给当前项目的全局使用,所以创建为静态的。
private static DataSource dataSource;
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
// 在项目启动时,即创建连接池对象,赋值给dataSource
static {
try {
Properties properties = new Properties();
InputStream inputStream = JDBCUtil.class.getClassLoader().getResourceAsStream("db.properties");
properties.load(inputStream);
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// 对外提供在连接池中获取连接的方法
public static Connection getConnection(){
try {
// 在ThreadLocal中获取Connection、
Connection connection = threadLocal.get();
// threadLocal里没有存储Connection,也就是第一次获取
if (connection == null) {
// 在连接池中获取一个连接,存储在threadLocal里。
connection = dataSource.getConnection();
threadLocal.set(connection);
}
return connection;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
// 对外提供回收连接的方法
public static void release(){
try {
Connection connection = threadLocal.get();
if(connection != null){
// 从threadLocal中移除当前已经存储的Connection对象
threadLocal.remove();
// 如果开启了事务的手动提交,操作完毕后,归还给连接池之前,要将事务的自动提交改为true
connection.setAutoCommit(true);
// 将Connection对象归还给连接池
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
DAO
DAO:Data Access Object,数据访问对象。一张表的操作对应一个DAO对象。
BaseDAO
/**
* 将共性的数据库的操作代码封装在BaseDAO里。
*/
public class BaseDAO {
/**
* 通用的增删改的方法。
*
* @param sql 调用者要执行的SQL语句
* @param params SQL语句中的占位符要赋值的参数
* @return 受影响的行数
*/
public int executeUpdate(String sql, Object... params) throws Exception {
// 1.通过JDBCUtil获取数据库连接
Connection connection = JDBCUtil.getConnection();
// 2.预编译SQL语句
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 4.为占位符赋值,执行SQL,接受返回结果
if (params != null && params.length > 0) {
for (int i = 0; i < params.length; i++) {
// 占位符是从1开始的。参数的数组是从0开始的
preparedStatement.setObject(i + 1, params[i]);
}
}
int row = preparedStatement.executeUpdate();
// 5.释放资源
preparedStatement.close();
// 如果开启了事务(autocommit = false),则不能释放
if (connection.getAutoCommit()) {
JDBCUtil.release();
}
//6.返回结果
return row;
}
/**
* 通用的查询:多行多列、单行多列、单行单列
* 多行多列:List<Employee>
* 单行多列:Employee
* 单行单列:封装的是一个结果。Double、Integer、。。。。。
* 封装过程:
* 1、返回的类型:泛型:类型不确定,调用者知道,调用时,将此次查询的结果类型告知BaseDAO就可以了。
* 2、 :通用,List 可以存储多个结果,也可以存储一个结果 get(0)
* 3、结果的封装:反射,要求调用者告知BaseDAO要封装对象的类对象。 Class
*/
public <T> List<T> executeQuery(Class<T> clazz, String sql, Object... params) throws Exception {
// 获取连接
Connection connection = JDBCUtil.getConnection();
// 预编译SQL语句
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 设置占位符的值
if (params != null && params.length > 0) {
for (int i = 0; i < params.length; i++) {
preparedStatement.setObject(i + 1, params[i]);
}
}
// 执行SQL,并接受返回的结果集
ResultSet resultSet = preparedStatement.executeQuery();
// 获取结果集中的元数据对象
// 包含了:列的数量、每个列的名称
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
List<T> list = new ArrayList<>();
//处理结果
while (resultSet.next()) {
//循环一次,代表有一行数据,通过反射创建一个对象
T t = clazz.newInstance();
//循环遍历当前行的列,循环几次,看有多少列
for (int i = 1; i <= columnCount; i++) {
//通过下表获取列的值
Object value = resultSet.getObject(i);
// 获取到的列的value值,这个值就是t这个对象中的某一个属性
// 获取当前拿到的列的名字 = 对象的属性名
String fieldName = metaData.getColumnLabel(i);
// 通过类对象和fieldName获取要封装的对象的属性
Field field = clazz.getDeclaredField(fieldName);
// 突破封装的private
field.setAccessible(true);
field.set(t, value);
}
list.add(t);
}
resultSet.close();
preparedStatement.close();
if (connection.getAutoCommit()) {
JDBCUtil.release();
}
return list;
}
/**
* 通用查询:在上面查询的集合结果中获取第一个结果。 简化了获取单行单列的获取、单行多列的获取
*/
public <T> T executeQueryBean(Class<T> clazz, String sql, Object... params) throws Exception {
List<T> list = this.executeQuery(clazz, sql, params);
if (list == null || list.size() == 0) {
return null;
}
return list.get(0);
}
}
具体表格的DAO
/**
* EmployeeDao这个类对应的是t_emp这张表的增删改查的操作
*/
public interface EmployeeDao {
List<Employee> selectAll();
Employee selectByEmpId(Integer empId);
int insert(Employee employee);
int update(Employee employee);
int delete(Integer empId);
}
public class EmployeeDaoImpl extends BaseDAO implements EmployeeDao {
@Override
public List<Employee> selectAll() {
try {
String sql = "SELECT emp_id empId,emp_name empName,emp_salary empSalary,emp_age empAge FROM t_emp";
return executeQuery(Employee.class,sql,null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Employee selectByEmpId(Integer empId) {
try {
String sql = "SELECT emp_id empId,emp_name empName,emp_salary empSalary,emp_age empAge FROM t_emp where emp_id = ?";
return executeQueryBean(Employee.class,sql,empId);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public int insert(Employee employee) {
try {
String sql = "INSERT INTO t_emp(emp_name,emp_salary,emp_age) VALUES (?,?,?)";
return executeUpdate(sql,employee.getEmpName(),employee.getEmpSalary(),employee.getEmpAge());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public int update(Employee employee) {
try {
String sql = "UPDATE t_emp SET emp_salary = ? WHERE emp_id = ?";
return executeUpdate(sql,employee.getEmpSalary(),employee.getEmpId());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public int delete(Integer empId) {
try {
String sql = "delete from t_emp where emp_id = ?";
return executeUpdate(sql,empId);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
事务
BankDao bankDao = new BankDaoImpl();
Connection connection=null;
try {
// 1.获取连接,将连接的事务提交改为手动提交
connection = JDBCUtilV2.getConnection();
connection.setAutoCommit(false);//开启事务,当前连接的自动提交关闭。改为手动提交!
// 2.操作减钱
bankDao.subMoney(1,100);
// 3.操作加钱
bankDao.addMoney(2,100);
// 4.前置的多次dao操作,没有异常,提交事务!
connection.commit();
} catch (Exception e) {
try {
connection.rollback();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}finally {
JDBCUtilV2.release();
}