JDBC学习!目标厦门实习!
全程Java Database Connectivity
Java连接数据库技术
文章目录
JDBC概念理解
通俗来讲是在Java代码中,使用JDBC提供的方法,可以发送字符串类型的SQL语言到数据库管理软件(MySQL等),并且获取语句执行结果,进而实现数据库数据CURD的操作的技术
Java提供规范的接口
数据库厂商根据java的jdbc规范接口,完成具体的实现驱动代码
JDBC的六个操作步骤
- 注册驱动 安装依赖的jar包
- 建立连接 connection
- 创建发送SQL语句的对象 statement
- 让statement对象发送SQL语句到数据库,并且获取返回结果集resultset
- 解析结果集resultset
- 销毁资源,顺序为:connection、statement、resultset
查询全部用户
建表sql语句
create database shangguigujdbc;
use shangguigujdbc;
create table t_user(
id int primary key auto_increment comment '用户主键',
account varchar(20) not null unique comment '账号',
password varchar(64) not null comment '密码',
nickname varchar(20) not null comment '昵称'
);
insert into t_user(account, password, nickname)
values
('root','123456','经理'),
('admin','666666','管理员');
代码
package com.shangguigu.api.statement;
import com.mysql.cj.jdbc.Driver;
import javax.sound.midi.Soundbank;
import java.sql.*;
/**
* @author 王修豪
* @version 1.0
* 使用statement查询t_user表下全部的数据
*/
public class StatementQueryPart {
/**
* 核心API
* TODO:
* DriverManger 驱动管理
* Connection
* Statement
* ResultSet
*
* @param args
*/
public static void main(String[] args) throws SQLException {
//1.注册驱动
/**
* TODO:
* 注册驱动
*/
DriverManager.registerDriver(new Driver());//是一个静态方法,可以直接调用
//2.
/**
* TODO:
* Java程序要和数据库创建连接
* Java程序要连接数据库,就要调用某个方法,方法也需要填入连接数据库的基本信息
* 数据库的IP地址 127.0.0.1
* 数据库端口号 3306
* 账号密码
* 连接数据库的名称
*/
/**
* 参数1:url
* jdbc:数据库厂商名://ip地址:port/数据库名
* jdbc:mysql://127.0.01:3306/shangguigujdbc
* 参数2:username 数据库账号
* 参数3:password 数据库密码
*
*/
//接口 = 实现类
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.01:3306/shangguigujdbc","root","2333");
//3.创建statement
Statement statement = connection.createStatement();
//4.发送sql语句,并且获取返回结果
String sql = "select * from t_user;";
ResultSet resultSet = statement.executeQuery(sql);
//5.进行结果集解析
//看看有没有下一行数据,有就可以获取
while(resultSet.next()){
int id = resultSet.getInt("id");
String account = resultSet.getString("account");
String password = resultSet.getString("password");
String nickname = resultSet.getString("nickname");
System.out.println(id+"--"+account+"--"+password+"--"+nickname);
}
//6.关闭资源
resultSet.close();
statement.close();
connection.close();
}
}
问题
出现的问题
问题寻找
在navicat下运行一下sql,发现运行不了
问题出在
拼写错误
登录
基于statement实现模拟登陆
package com.shangguigu.api.statement;
import com.mysql.cj.jdbc.Driver;
import jdk.nashorn.internal.runtime.regexp.joni.ast.StringNode;
import java.sql.*;
import java.util.Scanner;
import java.util.zip.CheckedOutputStream;
/**
* @author 王修豪
* @version 1.0
*
* TODO:
* 1.明确jdbc的使用流程和详细讲解内部设计api的方法
* 2.发现问题,引出preparedStatement
*
* TODO:
* 输入账号密码
* 进行数据库信息查询
* 反馈登陆成功还是登陆失败
*
* TODO:
* 键盘输入事件:收集账号和密码信息
* 注册成功
* 获取连接
* 创建statement
* 发送查询SQL语句,并获取返回结果
* 结果判断,显示登陆成功还是失败
* 关闭资源
*/
public class StatementUserLoginPart {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//1.获取用户输入信息
// 键盘输入事件
Scanner scanner = new Scanner(System.in);
System.out.println("请输入账号");
String account = scanner.nextLine();
System.out.println("请输入密码");
String password = scanner.nextLine();
//2.注册驱动
/**
* 方案1:
* DriverManager.registerDriver(new Driver());
*
* 问题:注册两次驱动
* 一次是DriverManager.registerDriver(new Driver());方法本身注册一次
* 另一次是Driver内容的静态代码块会注册一次
* 解决:只想注册一次驱动
* 只出发静态代码块即可 Driver
* 触发静态代码块:
* 类加载机制:类加载的时刻,会触发静态代码块
* 类加载的过程:1.加载:【class文件->jvm虚拟机的class对象】
* 2.连接【验证是否有错->准备 静态变量默认值-> 解析 这个时候触发静态代码块】
* 3.初始化 给静态属性赋真实值
* 触发 类加载:
* 1.new 关键字
* 2.调用静态方法
* 3.调用静态属性
* 4.default默认实现
* 5.反射
* 6.子类出发父类
* 7.程序入口main
*/
// 方案一:淘汰
// DriverManager.registerDriver(new Driver());
// 方案二:不太优雅,这是mysql的驱动,换成Oracle麻烦 淘汰
// new Driver();
//使用反射的方法,字符串 提取到外部的配置文件,这样的好处是字符串信息可以提取到外部的配置文件 -> 不改变代码的情况下,完成数据库驱动的切换
Class.forName("com.mysql.cj.jdbc.Driver");//触发类加载,触发静态代码块的调用
//2.获取数据库连接
/**
* getConnection(1,2,3)方法,是一个重载方法
* 允许开发者用不同的形式传入数据库连接的核心参数
*
* 核心属性:
* 1.数据库软件所在的主机的ip地址 127.0.0.1
* 2.数据库软件所在主机的端口号 3306
* 3.连接具体哪个库
* 4.连接的账号
* 5.连接的密码
* 6.可选的信息
* 三个参数:
* String url 数据库所在的信息,丽娜姐的具体库,以及其他的可选信息
* 语法:jdbc:数据库管理软件名称[MySQL, oracle]://IP地址|主机名:port端口号/数据库名?key=value
* 具体: jdbc:mysql://127.0.0.1:3306/shangguigujdbc
* 如果数据库软件安装到本机,且端口号3306,可以进行一些省略
* jdbc:mysql:///shangguigujdbc
* String user
* String password
* 两个参数:
* String url 同上
* Properties info 存储账号密码类似map
* 一个参数:
* String url 账号密码填到可选信息
*/
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/shangguigujdbc", "root", "2333");
//3.发送SQL语句的statement对象
//statement可以发送sql语句到数据库,并且获得返回结果
Statement statement = connection.createStatement();
//4.发送sql语句(1.编写sql 2.发送)
String sql = "SELECT * FROM t_user WHERE account = '"+account+"' AND PASSWORD = '"+password+"';";
//不能防止注入攻击
/**
* SQL分类: DDL(容易创建, 修改, 修改) DML(插入, 修改, 删除) DQL(查询) DCL(权限控制) TPL(事务控制语言)
*
* executeUpdate
* 返回值: int
* 情况一: DML 返回影响行数
* 情况二:非DML 返回0
*
* executeQuery
* 返回值: resultSet 结果封装对象
*
*/
// int i = statement.executeUpdate(sql);
ResultSet resultSet = statement.executeQuery(sql);
//5.查询结果解析
/**
*1.逐行获取数据
* resultSet内部包含一个游标,指定当前行数据
* 默认游标指定的是第一行数据之前
* 可以用next方法向后移动一行游标
* 如果我们有很多行数据,我们可以使用while(next){获取每一行的数据}
*
* boolean = next(); 如果为true表示更多行数据,并向下移动
* 如果为false,表示没有更多行
* TODO:
* 移动光标的方法还有很多,但是只记住next即可
*2.获取行的列数据
* resultSet.get类型(填写String(名称)或者int(下标,从1开始))
*/
if(resultSet.next()){
//指定当前行
int id = resultSet.getInt(1);
String account1 = resultSet.getString("account");
String password1 = resultSet.getString(3);
String nikename = resultSet.getString(4);
System.out.println("登录成功: nikename:" + nikename + " -- id:"+id+"-- account:"+account1);
}else{
System.out.println("登陆失败");
}
//关闭资源
resultSet.close();
statement.close();
connection.close();
}
}
存在问题
-
SQL语句需要字符串拼接,比较麻烦
-
只能拼接字符串类型,其他数据库类型无法处理
-
可能发生注入攻击
静态SQL语句,适用于没用动态值,
基于preparedStatement实现模拟登陆
preparedStatement是重点
package com.shangguigu.api.statement;
import java.sql.*;
import java.util.Scanner;
/**
* @author 王修豪
* @version 1.0
*/
public class PSUserLoginPart {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
//1,收集用户信息
Scanner scanner = new Scanner(System.in);
System.out.println("请输入账号");
String account = scanner.nextLine();
System.out.println("请输入密码");
String password = scanner.nextLine();
//2注册驱动 获取连接
Class.forName("com.mysql.cj.jdbc.Driver");
//获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/shangguigujdbc", "root", "2333");
//3.编写sql,问号? 是占位符
String sql = "SELECT * FROM t_user WHERE account = ? and password = ? ;";
//4.创建预编译praparedStatement 并且设置sql语句结果
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//5.单独的占位符进行赋值
/**
* 参数1:index占位符的位置
* 参数2:占位符的数值
*/
preparedStatement.setString(1,account);
preparedStatement.setString(2,password);
//6.发送sql语句,并获取返回结果
ResultSet resultSet = preparedStatement.executeQuery();
//7.遍历结果集
while (resultSet.next()){
System.out.println(resultSet.getString(1));
System.out.println(resultSet.getString(2));
}
//8.关闭资源
resultSet.close();
preparedStatement.close();
connection.close();
}
}
preparedStatement的crud操作
package com.shangguigu.api.statement;
import org.junit.Test;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.CheckedOutputStream;
/**
* @author 王修豪
* @version 1.0
*/
public class PSCURDPart {
@Test
public void testInsert() throws ClassNotFoundException, SQLException {
/**
* t_user表插入一条数据
*/
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取链接
Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/shangguigujdbc","root","2333");
//3.编写sql,动态值使用 ? 代替
String sql = "insert into t_user(account,password,nickname) values(?,?,?)";
//4.创建preparedStatement,传入sql
PreparedStatement ps = conn.prepareStatement(sql);
//5.创建占位符
ps.setObject(1, "test");
ps.setObject(2, "test");
ps.setObject(3, "二狗子");
//6.发送sql语句,并获取返回结果
int i = ps.executeUpdate();
//7.判断结果
if(i>0){
System.out.println("插入成功");
}else{
System.out.println("插入失败");
}
//8.关闭资源
ps.close();
conn.close();
}
@Test
public void testUpdate() throws SQLException, ClassNotFoundException {
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取链接
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/shangguigujdbc","root","2333");
//3.编写sql,动态值使用 ? 代替
String sql = "update t_user set nickname =?,account=? where id =?";
//4.创建preparedStatement,传入sql
PreparedStatement ps = connection.prepareStatement(sql);
//5.创建占位符
ps.setObject(1,"hhhh");
ps.setObject(2,"xixi");
ps.setObject(3,3);
ps.executeUpdate();
ps.close();
connection.close();
}
@Test
public void testDelete() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/shangguigujdbc","root","2333");
String sql = "delete from t_user where id=?";
PreparedStatement ps = connection.prepareStatement(sql);
ps.setObject(1,3);
ps.executeUpdate();
ps.close();
connection.close();
}
/**
* 查询所有用户,封装到一个List<Map>list中
* id account password nickname
* 把一行数据封装到map中 多个map放到list中
*
* 难点:获取列的名称
* @throws ClassNotFoundException
* @throws SQLException
*/
@Test
public void testSelect() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/shangguigujdbc","root","2333");
String sql = "select id,account,password,nickname from t_user";
PreparedStatement ps = connection.prepareStatement(sql);
ResultSet resultSet = ps.executeQuery();
/**
* TODO回顾:
* resultSet:有行和列
* 利用next()指向数据行
* 获取数据列的数据
*/
List<Map> list = new ArrayList<Map>();
//获取列的信息对象
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
//获取列的数量
int columnCount = resultSetMetaData.getColumnCount();
while(resultSet.next()){
Map map = new HashMap<String,Object>();
//要从1开始! 因为数据库取值是从1开始的
for(int i=1;i<=columnCount;i++){
//需要获取列的名称 或缺列的信息从ResultSetMetaData中获取
//getColumnLabel:先获取别名,没有别名再获取名称 getColumnName:只会获取列的名称
String columnLabel = resultSetMetaData.getColumnLabel(i);
Object value = resultSet.getObject(i);
map.put(columnLabel,value);
//获取列的类型
}
list.add(map);
}
for()
resultSet.close();
ps.close();
connection.close();
}
}
PreparedStatement使用方法总结
使用步骤总结
-
注册驱动
第一种:DriverManager.registerDriver(new Driver());//是一个静态方法,可以直接调用,但是会注册两次 第二种:Class.forName("com.mysql.cj.jdbc.Driver");//反射触发
-
获取链接
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/shangguigujdbc","root","2333"); 有三种
-
编写sql语句
-
创建PreparedStatement对象,并且传入sql
-
设置占位符
-
发送SQL语句,获取运行结果
两种 int rows = executeUpdate();//非DQL ResultSet = executeQuery();//DQL
-
结果解析
移动光标 next() 获取列数据 //获取列的数量 int columnCount = resultSetMetaData.getColumnCount(); //getColumnLabel:先获取别名,没有别名再获取名称 getColumnName:只会获取列的名称 String columnLabel = resultSetMetaData.getColumnLabel(i); Object value = resultSet.getObject(i);
-
关闭资源
全新JDBC扩展提升
自增长主键回显显示
获取数据库自增长的主键
-
创建statement的时候告诉他要返回自增主键
PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
-
取值,获取结果集
ResultSet generatedKeys = preparedStatement.getGeneratedKeys();
/**
* TODO:
* t_user插入一条数据 并且获取数据库自增长的主键
*
*/
@Test
public void returnPrimaryKey() throws ClassNotFoundException, SQLException {
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/shangguigujdbc","root","2333");
//3.编写SQL语句
String sql = "INSERT INTO t_user (account,password,nickname) values(?,?,?);";
//4,创建statement
/**
* 第一步:
* 在创建statement的时候,告诉statement要传回来
*/
PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
//5.设置占位符
preparedStatement.setObject(1,"test1");
preparedStatement.setObject(2,"123456");
preparedStatement.setObject(3,"我去");
//6,发送sql,获取结果
int i = preparedStatement.executeUpdate();
//7.结果解析
if(i>0){
System.out.println("插入成功");
/**
* 第二步:
* 取值,获取装GeneratedKeys的结果集
*/
ResultSet generatedKeys = preparedStatement.getGeneratedKeys();
generatedKeys.next();
int id = generatedKeys.getInt(1);
System.out.println("主键是"+id);
}else {
System.out.println("插入失败");
}
//8.关闭资源
preparedStatement.close();
connection.close();
}
批量插入数据优化
批量操作的原理是在后面追加,注意不要写分号,之后通过addBatch设置占位符 最后统一操作
/**
* TODO:
* 使用批量插入的方法插入10000条数据
* 1.连接路径添加 允许批量插入
* 2.sql不能写分号
* 3,批量添加 addBatch();
* 4.统一执行 executeBatch();
*/
@Test
public void testBatchInsert10000() throws ClassNotFoundException, SQLException {
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接,需要追加参数rewriteBatchedStatements = true
Connection connection = DriverManager.getConnection(
"jdbc:mysql://127.0.0.1:3306/shangguigujdbc?rewriteBatchedStatements = true","root","2333");
//3.编写SQL语句,不能写分号
String sql = "INSERT INTO t_user (account,password,nickname) values(?,?,?)";
//4,创建statement
/**
* 第一步:
* 在创建statement的时候,告诉statement要传回来
*/
PreparedStatement preparedStatement = connection.prepareStatement(sql);
long start = System.currentTimeMillis();
for(int j=2;j<=10001;j++){
//5.设置占位符
preparedStatement.setObject(1,"test"+Integer.toString(j));
preparedStatement.setObject(2,"123456");
preparedStatement.setObject(3,"我去");
//6.不执行sql, 直接追加到values的后面
preparedStatement.addBatch();
}
//7.最终统一执行 批量操作
preparedStatement.executeBatch();
long end = System.currentTimeMillis();
System.out.println("需要的时间"+(end-start));
//需要的时间206ms
//8.关闭资源
preparedStatement.close();
connection.close();
}
JDBC中数据库事务的实现
转账的动作分开写
Service存储业务,Dao存储数据访问对象
测试类调用service,存储表的业务方法,接着调用表的操作方法,存储数据库操作
BankService.java
connection.setAutoCommit(false); 设置事务
public class BankService {
@Test
public void start() throws SQLException, ClassNotFoundException {
transfer("ergouzi","lvdandan",500);
}
/**
* TODO
* 事务添加在事务方法中
* 利用try catch代码块,开启事务 提交事务 事务回滚
* 将connection传入dao层
* dao层只负责数据库操作
* @param addAccount
* @param subAccount
* @param money
* @throws SQLException
* @throws ClassNotFoundException
*/
public void transfer(String addAccount,String subAccount,int money) throws SQLException, ClassNotFoundException {
BankDao bankDao = new BankDao();
//一个事务的最基本要求,必须是同一个连接对象 connection
//一个转账方法 属于一个事务 (加钱,减钱)
//所以要在这里获取连接
Class.forName("com.mysql.cj.jdbc.Driver");
//2.建立连接
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/shangguigujdbc","root","2333");
try{
//开启事务
//关闭事务自动提交
connection.setAutoCommit(false);
//执行动作
bankDao.add(addAccount,money,connection);
System.out.println("----------");
bankDao.sub(subAccount,money,connection);
//事务提交
connection.commit();
}catch (Exception e){
//事务回滚
connection.rollback();
throw e;
}finally {
connection.close();
}
}
}
BankDao.java
package com.shangguigu.api.transaction;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* @author 王修豪
* @version 1.0
*/
public class BankDao {
/**
* 加钱的数据库操作方法
* @param account
* @param money
*/
public void add(String account,int money,Connection connection) throws ClassNotFoundException, SQLException {
//1.注册驱动
// Class.forName("com.mysql.cj.jdbc.Driver");
// //2.建立连接
// Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/shangguigujdbc","root","2333");
//3.编写sql语句
String sql = "update t_bank set money = money + ? where account=?";
//4.创建statement
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//5.设置占位符
preparedStatement.setObject(1,money);
preparedStatement.setObject(2,account);
//6.发送sql语句
int i = preparedStatement.executeUpdate();
//7.关闭资源
preparedStatement.close();
System.out.println("加钱成功");
}
/**
* 减钱的数据库操作方法
* @param account
* @param money
*/
public void sub(String account,int money,Connection connection) throws SQLException, ClassNotFoundException {
//1.注册驱动
// Class.forName("com.mysql.cj.jdbc.Driver");
// //2.建立连接
// Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/shangguigujdbc","root","2333");
// //3.编写sql语句
String sql = "update t_bank set money = money - ? where account=?";
//4.创建statement
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//5.设置占位符
preparedStatement.setObject(1,money);
preparedStatement.setObject(2,account);
//6.发送sql语句
int i = preparedStatement.executeUpdate();
//7.关闭资源
preparedStatement.close();
System.out.println("减钱成功");
}
}
连接池
连接池工作原理
实践开发中,连接创建和销毁的时间大于使用时间,这样不合理
连接池可以容纳一定数量的连接所想,只需要获取和回收连接,不需要创建和销毁连接,节约时间
国货之光Druid德鲁伊连接池
硬编码方法
直接使用代码设置连接池连接参数方法
/**
* 硬编码
* 直接使用代码设置连接池连接参数方式
* 步骤:
* 1.创建连接池对象
* 2.设置连接池参数【必须和非必须】
* 3.获取连接 所有连接池都一样
* 4.回收连接 所有连接池都一样
*/
public void testHard() throws SQLException {
//创建连接池对象
DruidDataSource dataSource = new DruidDataSource();
//设置参数
//必须设置的参数
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/shangguigujdbc");
dataSource.setUsername("root");
dataSource.setPassword("2333");
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
//非必须设置的参数
dataSource.setInitialSize(5);//初始化连接数量
dataSource.setMaxActive(10);//最大数量
//获取连接
Connection connection = dataSource.getConnection();
//数据库curd
//回收链接
connection.close();//连接池的close是回收
}
软编码方法
将信息写在外部配置文件中
druid.properties
driverClassName = com.mysql.cj.jdbc.Driver
username = root
password = 2333
url = jdbc:mysql://127.0.0.1:3306/shangguigujdbc
/**
* 通过读取外部配置文件的方法,实例化druid连接池对象
*/
public void testSoft() throws Exception {
//1.读取外部配置文件
Properties properties = new Properties();
//src下的文件,可以使用类加载器提供的方法
InputStream ips = DruidUsePart.class.getClassLoader().getResourceAsStream("druid.properties");
properties.load(ips);
//2.使用连接池的工具类的工程模式,创建连接池
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
Connection connection = dataSource.getConnection();
//数据库crud
//回收连接
connection.close();
}
获取连接工具类
线程本地变量ThreadLocal
可以为同一个线程存储共享变量,不需要传connection, 更加优雅
三个方法:
1.get: 获取ThreadLocal中当前线程中共享变量的值
2.set: 设置ThreadLocal中当前线程中共享变量的值
3.remove: 移除ThreadLocal中当前线程中共享变量的值
/**
* @Author 赵伟风
* Description:
*
* v1.0版本工具类
* 内部包含一个连接池对象,并且对外提供获取连接和回收连接的方法!
*
* 小建议:
* 工具类的方法,推荐写成静态,外部调用会更加方便!
*
* 实现:
* 属性 连接池对象 [实例化一次]
* 单例模式
* static{
* 全局调用一次
* }
* 方法
* 对外提供连接的方法
* 回收外部传入连接方法
*
*
* TODO:
* 利用线程本地变量,存储连接信息! 确保一个线程的多个方法可以获取同一个connection!
* 优势: 事务操作的时候 service 和 dao 属于同一个线程,不同再传递参数了!
* 大家都可以调用getConnection自动获取的是相同的连接池!
*
*/
public class JdbcUtilsV2 {
private static DataSource dataSource = null; //连接池对象
private static ThreadLocal<Connection> tl = new ThreadLocal<>();
static{
//初始化连接池对象
Properties properties = new Properties();
InputStream ips = JdbcUtilsV2.class.getClassLoader().getResourceAsStream("druid.properties");
try {
properties.load(ips);
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 对外提供连接的方法
* @return
*/
public static Connection getConnection() throws SQLException {
//线程本地变量中是否存在
Connection connection = tl.get();
//第一次没有
if (connection == null) {
//线程本地变量没有,连接池获取
connection = dataSource.getConnection();
tl.set(connection);
}
return connection;
}
public static void freeConnection() throws SQLException {
Connection connection = tl.get();
if (connection != null) {
tl.remove(); //清空线程本地变量数据
connection.setAutoCommit(true); //事务状态回顾 false
connection.close(); //回收到连接池即可
}
}
}
BaseDao
DAO接口及其实现类对所有表的CRUD代码重复度很高,所以可以抽取公共代码,给这些DAO的实现类抽取一个公共的父类,称之为BaseDao
package com.atguigu.cms.utils;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
public abstract class BaseDao {
/*
通用的增、删、改的方法
String sql:sql
Object... args:给sql中的?设置的值列表,可以是0~n
*/
protected int update(String sql,Object... args) throws SQLException {
// 创建PreparedStatement对象,对sql预编译
Connection connection = JDBCTools.getConnection();
PreparedStatement ps = connection.prepareStatement(sql);
//设置?的值
if(args != null && args.length>0){
for(int i=0; i<args.length; i++) {
ps.setObject(i+1, args[i]);//?的编号从1开始,不是从0开始,数组的下标是从0开始
}
}
//执行sql
int len = ps.executeUpdate();
ps.close();
//这里检查下是否开启事务,开启不关闭连接,业务方法关闭!
//没有开启事务的话,直接回收关闭即可!
if (connection.getAutoCommit()) {
//回收
JDBCTools.free();
}
return len;
}
/*
通用的查询多个Javabean对象的方法,例如:多个员工对象,多个部门对象等
这里的clazz接收的是T类型的Class对象,
如果查询员工信息,clazz代表Employee.class,
如果查询部门信息,clazz代表Department.class,
*/
protected <T> ArrayList<T> query(Class<T> clazz,String sql, Object... args) throws Exception {
// 创建PreparedStatement对象,对sql预编译
Connection connection = JDBCTools.getConnection();
PreparedStatement ps = connection.prepareStatement(sql);
//设置?的值
if(args != null && args.length>0){
for(int i=0; i<args.length; i++) {
ps.setObject(i+1, args[i]);//?的编号从1开始,不是从0开始,数组的下标是从0开始
}
}
ArrayList<T> list = new ArrayList<>();
ResultSet res = ps.executeQuery();
/*
获取结果集的元数据对象。
元数据对象中有该结果集一共有几列、列名称是什么等信息
*/
ResultSetMetaData metaData = res.getMetaData();
int columnCount = metaData.getColumnCount();//获取结果集列数
//遍历结果集ResultSet,把查询结果中的一条一条记录,变成一个一个T 对象,放到list中。
while(res.next()){
//循环一次代表有一行,代表有一个T对象
T t = clazz.newInstance();//要求这个类型必须有公共的无参构造
//把这条记录的每一个单元格的值取出来,设置到t对象对应的属性中。
for(int i=1; i<=columnCount; i++){
//for循环一次,代表取某一行的1个单元格的值
Object value = res.getObject(i);
//这个值应该是t对象的某个属性值
//获取该属性对应的Field对象
// String columnName = metaData.getColumnName(i);//获取第i列的字段名
String columnName = metaData.getColumnLabel(i);//获取第i列的字段名或字段的别名
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);//这么做可以操作private的属性
field.set(t, value);
}
list.add(t);
}
res.close();
ps.close();
//这里检查下是否开启事务,开启不关闭连接,业务方法关闭!
//没有开启事务的话,直接回收关闭即可!
if (connection.getAutoCommit()) {
//回收
JDBCTools.free();
}
return list;
}
protected <T> T queryBean(Class<T> clazz,String sql, Object... args) throws Exception {
ArrayList<T> list = query(clazz, sql, args);
if(list == null || list.size() == 0){
return null;
}
return list.get(0);
}
}