JDBC概述
JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口(一组API),定义了用来访问数据库的标准Java类库,(java.sql,javax.sql)使用这些类库可以以一种标准的方法、方便地访问数据库资源。
JDBC为访问不同的数据库提供了一种统一的途径,为开发者屏蔽了一些细节问题。
JDBC的目标是使Java程序员使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统,这样就使得程序员无需对特定的数据库系统的特点有过多的了解,从而大大简化和加快了开发过程。
如果没有JDBC,那么Java程序访问数据库时是这样的
有了JDBC可以达到这样的效果
JDBC制定了一组规范的接口,各大数据库厂商都提供的自家数据库JDBC驱动
JDBC驱动:就是JDBC接口的实现类
所以,JAVA程序员在操作数据库时,只要加载对应数据库驱动后
调用JDBC定义的接口方法,就可以实现对各个数据的操作
不需要去了解数据库厂家的具体实现和细节
@Test
public void test1() throws SQLException {
//1. 调用mysql的驱动实例化Driver
Driver driver = new com.mysql.cj.jdbc.Driver();
//2. 配置账号密码
Properties info = new Properties();
info.setProperty("user","root");
info.setProperty("password","1234");
/** 3. 数据库连接地址
* jdbc:mysql : 连接协议
* localhost : IP地址
* 3306:mysql端口号
* test :数据库的名字
*/
String url = "jdbc:mysql://localhost:3306/test";
//4. 打开连接
Connection connect = driver.connect(url, info);
System.out.println(connect);
}
方式2是对方式1的一个迭代,方式1中出现了第三方的api
方式2则是利用反射将去实例化类,从而动态去实例化第三方驱动
这样可以更加灵活的切换连接的数据库,例如从mysql到orcale的切换
public void test2() throws Exception {
//1. 通过反射获取驱动实现类
Class clazz = Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver = (Driver) clazz.newInstance();
//2. 配置账号密码
Properties info = new Properties();
info.setProperty("user","root");
info.setProperty("password","1234");
/** 3. 数据库连接地址
* jdbc:mysql : 连接协议
* localhost : IP地址
* 3306:mysql端口号
* test :数据库的名字
*/
String url = "jdbc:mysql://localhost:3306/test";
//4. 打开连接
Connection connect = driver.connect(url, info);
System.out.println(connect);
}
使用DriverManager代替Driver进行注册和连接
@Test
public void test3() throws Exception {
//1. 通过反射获取驱动实现类
Class clazz = Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver = (Driver) clazz.newInstance();
//2. 注册Driver驱动
DriverManager.registerDriver(driver);
//3. 配置账号密码
Properties info = new Properties();
info.setProperty("user","root");
info.setProperty("password","1234");
/** 4. 数据库连接地址
* jdbc:mysql : 连接协议
* localhost : IP地址
* 3306:mysql端口号
* test :数据库的名字
*/
String url = "jdbc:mysql://localhost:3306/test";
//5. 连接数据库
Connection con = DriverManager.getConnection(url, info);
System.out.println(con);
}
省略显示的驱动实例化和注册的过程,只需要加载驱动类文件
静态代码块自动实例化和注册
@Test
public void test4() throws Exception {
//1. 加载驱动类文件
Class.forName("com.mysql.cj.jdbc.Driver");
//2. 配置账号密码、数据库地址
Properties info = new Properties();
info.setProperty("user","root");
info.setProperty("password","1234");
String url = "jdbc:mysql://localhost:3306/test";
//3. 连接数据库
Connection con = DriverManager.getConnection(url, info);
System.out.println(con);
}
为什么可以省略驱动注册的步骤呢?
//mysql的驱动源码,
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
**static** {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
实际开发中最常用的方式
通过配置文件读取数据库的连接信息
@Test
public void test5() throws Exception {
//1. 通过类加载器 获取 数据库文件中的配置信息
ClassLoader loader = JDBCTest1.class.getClassLoader();
InputStream is = loader.getResourceAsStream("jdbcTest.properties");
Properties info = new Properties();
info.load(is);
String user = info.getProperty("user");
String password = info.getProperty("password");
String url = info.getProperty("url");
String driver = info.getProperty("driver");
//2. 通过反射加载驱动类
Class.forName(driver);
//3. 连接数据库
Connection con = DriverManager.getConnection(url, user, password);
System.out.println(con);
}
object relationship mapping 对象关系映射
一个数据表对应一个java类
表中一条记录对应java类的一个对象
表中一个字段对应java类的一个属性
int excuteUpdate(String sql):执行更新操作INSERT、UPDATE、DELETE
ResultSet executeQuery(String sql):执行查询操作SELECT
String sql = "SELECT user,password FROM user_table WHERE USER = '" + userName + "' AND PASSWORD = '" + password + "'";
#user:1' OR
#password:='1' OR '1' = '1
SELECT USER,PASSWORD FROM user_table WHERE USER = '1' OR ' AND PASSWORD = '='1' OR '1' = '1'
对于 Java 而言,要防范 SQL 注入,只要用 PreparedStatement(从Statement扩展而来) 取代 Statement 就可以了。
public class StatementTest {
// 使用Statement的弊端:需要拼写sql语句,并且存在SQL注入的问题
@Test
public void testLogin() {
Scanner scan = new Scanner(System.in);
System.out.print("用户名:");
String userName = scan.nextLine();
System.out.print("密 码:");
String password = scan.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 = '" + userName + "' AND PASSWORD = '" + password
+ "'";
User user = get(sql, User.class);
if (user != null) {
System.out.println("登陆成功!");
} else {
System.out.println("用户名或密码错误!");
}
}
// 使用Statement实现对数据表的查询操作
public <T> T get(String sql, Class<T> clazz) {
T t = null;
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
// 1.加载配置文件
InputStream is = StatementTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties pros = new Properties();
pros.load(is);
// 2.读取配置信息
String user = pros.getProperty("user");
String password = pros.getProperty("password");
String url = pros.getProperty("url");
String driverClass = pros.getProperty("driverClass");
// 3.加载驱动
Class.forName(driverClass);
// 4.获取连接
conn = DriverManager.getConnection(url, user, password);
st = conn.createStatement();
rs = st.executeQuery(sql);
// 获取结果集的元数据
ResultSetMetaData rsmd = rs.getMetaData();
// 获取结果集的列数
int columnCount = rsmd.getColumnCount();
if (rs.next()) {
t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
// //1. 获取列的名称
// String columnName = rsmd.getColumnName(i+1);
// 1. 获取列的别名
String columnName = rsmd.getColumnLabel(i + 1);
// 2. 根据列名获取对应数据表中的数据
Object columnVal = rs.getObject(columnName);
// 3. 将数据表中得到的数据,封装进对象
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(t, columnVal);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭资源
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st != null) {
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return null;
}
}
public class JDBCUtils {
public static Connection getConnection() throws Exception{
//通过类加载器 获取 数据库文件中的配置信息
ClassLoader loader = ClassLoader.getSystemClassLoader();
InputStream is = loader.getResourceAsStream("jdbc.properties");
Properties info = new Properties();
info.load(is);
String user = info.getProperty("user");
String password = info.getProperty("password");
String url = info.getProperty("url");
String driver = info.getProperty("driver");
//通过反射加载驱动类
Class.forName(driver);
//连接数据库
Connection con = DriverManager.getConnection(url, user, password);
return con;
}
public static void closeResource(Connection con, Statement ps) {
try {
if (ps != null)
ps.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
if (con != null)
con.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
@Test
public void insertTest(){
Connection conn = null;
PreparedStatement ps = null;
try {
//开启连接
conn = JDBCUtils.getConnection();
//1.SQL预编译、获取PrepareStatement
String sql = "INSERT INTO customers(NAME,email,birth) VALUES(?,?,?);";
ps = conn.prepareStatement(sql);
//2.填充占位符
ps.setString(1,"哪吒");
ps.setString(2,"[email protected]");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
java.util.Date date = sdf.parse("1000-01-01");
ps.setDate(3,new Date(date.getTime()));
//3. 执行sql
ps.*execute*();
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭连接
JDBCUtils.closeResource(conn,ps);
}
}
删除操作
@Test
public void deleteTest(){
Connection conn = null;
PreparedStatement ps = null;
try {
//开启连接
conn = JDBCUtils.getConnection();
//1.SQL预编译、获取PrepareStatement
String sql = "delete from customers where id = ?";
ps = conn.prepareStatement(sql);
//2.填充占位符
ps.setObject(1,18);
//3. 执行sql
ps.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭连接
JDBCUtils.closeResource(conn,ps);
}
}
修改操作
@Test
public void updateTest(){
Connection conn = null;
PreparedStatement ps = null;
try {
//开启连接
conn = JDBCUtils.getConnection();
//1.SQL预编译、获取PrepareStatement
String sql = "update customers set name = ? where id = ?;";
ps = conn.prepareStatement(sql);
//2.填充占位符
ps.setObject(1,"莫扎特11");
ps.setObject(2,18);
//3. 执行sql
ps.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭连接
JDBCUtils.closeResource(conn,ps);
}
}
通过上方增删改的例子,可以看出,变化的只有SQL和填充的占位符
所以可以将增删改操作写成一个通用的方法,每次只要传入SQL和可变参数即可
@Test
public void commonUpdate(String sql, Object... args){
Connection conn = null;
PreparedStatement ps = null;
try {
//数据库建立连接
conn = JDBCUtils.getConnection();
//预编译SQL
ps = conn.prepareStatement(sql);
//填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//执行SQL
ps.execute();
} catch (Exception e) {
e.printStackTrace();
}
finally {
//关闭连接
JDBCUtils.closeResource(conn,ps);
}
}
@Test
public void queryTest() throws Exception {
//连接数据库
Connection conn = JDBCUtils.getConnection();
//预编译SQL
String sql = "SELECT id,`NAME`,email,birth FROM customers where id = ?;";
PreparedStatement ps = conn.prepareStatement(sql);
//填充占位符
ps.setObject(1,1);
//执行,获取结果集
ResultSet res = ps.executeQuery();
if(res.next()){ //判断结果集下一条是否有数据,有数据返回Ture,并指针下移,如果返回false,指针不会下移
int id = res.getInt(1);
String name = res.getString(2);
String email = res.getString(3);
Date birth = res.getDate(4);
//打印方式一
System.out.println("id:" + id + " name:" + name + " email:" + email+ " birth:" + birth);
//打印方式二
Object[] data = new Object[]{id,name,email,birth};
System.out.println(Arrays.toString(data));
//打印方式三
Customer customer = new Customer(id, name, email, birth);
System.out.println(customer);
}
//关闭资源
JDBCUtils.closeResource(conn,ps,res);
}
查询与增删改的区别,查询返回结果集ResultSet
使用next方法检查结果集中是否存在数据
有数据则指针下移看,指向数据,无数据则指针不下移
ResultSet.next方法类似iterate.hasNext 与 iterate.next的功能综合版本,但是不会返回数据
是通过索引获取当前行的数据
推荐使用方式三的方式存储数据:存储到对象之中
针对单表进行查询
//Customer 表进行查询
public Customer CommonqueryTest(String sql,Object... args) throws Exception {
//连接数据库
Connection conn = JDBCUtils.getConnection();
//预编译SQL
PreparedStatement ps = conn.prepareStatement(sql);
//填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//返回结果集
ResultSet res = ps.executeQuery();
//获取结果集的元数据
ResultSetMetaData rsmd = res.getMetaData();
int columnCount = rsmd.getColumnCount();
//判断结果集中是否存在对象,将指针指向数据行
if(res.next())
{
Customer customer = new Customer();
for (int i = 0; i < columnCount; i++) {
//获取列名、值
Object columnValue = res.getObject(i + 1);
String columnName = rsmd.getColumnName(i + 1); //使用getColumnLabel代替
//反射类字段值
Field field = Customer.class.getDeclaredField(columnName);
//访问私有字段
field.setAccessible(true);
//设置值
field.set(customer,columnValue);
}
return customer;
}
return null;
}
针对通用表进行查询
public <T> T CommonqueryTest2(Class<T> clazz, String sql,Object... args) throws Exception {
//连接数据库
Connection conn = JDBCUtils.getConnection();
//预编译
PreparedStatement ps = conn.prepareStatement(sql);
//填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//查询获取结果集
ResultSet res = ps.executeQuery();
//获取结果集的元数据
ResultSetMetaData rsmd = res.getMetaData();
int columnCount = rsmd.getColumnCount();
//获取结果集
if(res.next())
{
T t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
//获取列名、值
Object columnValue = res.getObject(i + 1);
String columnName = rsmd.getColumnName(i + 1);
//反射类字段值
Field field = Customer.class.getDeclaredField(columnName);
//访问私有字段
field.setAccessible(true);
//设置值
field.set(t,columnValue);
}
return t;
}
return null;
}
返回查询的集合
public <T> List<T> CommonqueryTest3(Class<T> clazz, String sql, Object... args) throws Exception {
//连接数据库
Connection conn = JDBCUtils.getConnection();
//预编译
PreparedStatement ps = conn.prepareStatement(sql);
//填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//查询获取结果集
ResultSet res = ps.executeQuery();
//获取结果集的元数据
ResultSetMetaData rsmd = res.getMetaData();
int columnCount = rsmd.getColumnCount();
ArrayList<T> list = new ArrayList<>();
//获取结果集
while(res.next())
{
T t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
//获取列名、值
Object columnValue = res.getObject(i + 1);
String columnName = rsmd.getColumnName(i + 1);
//反射类字段值
Field field = Customer.class.getDeclaredField(columnName);
//访问私有字段
field.setAccessible(true);
//设置值
field.set(t,columnValue);
}
list.add(t);
}
return list;
}
※ 通常不使用Throws ,而是Try Catch处理异常
数据库字段名要与Java类中属性名一致
数据库字段名要与Java类中属性名不一致:则必须在SQL语句中给字段起别名
思维导图
# 1.Statement中,直接输入SQL语句进行执行,就会出现SQL注入问题
SELECT USER,PASSWORD FROM user_table WHERE USER = '1' OR ' AND PASSWORD = '='1' OR '1' = '1'
# 2.在PreparStatement中则是先进行SQL的预编译, 使用占位符,不用拼串,后续填充占位符,也不会改变SQL原有的逻辑,解决SQL注入
String sql = "SELECT * FROM admin WHERE username = ? AND PASSWORD = ?;";
PreparedStatement ps = conn.prepareStatement(sql);
Statement批量操作:执行一次SQL语句,需要校验一次SQL
repaerStatement批量操作:无论执行多少次SQL,都只校验一次,效率更高
DBServer会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。
在statement语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身不能匹配,没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存。这样每执行一次都要对传入的语句编译一次。
(语法检查,语义检查,翻译成二进制命令,缓存)
实际使用中根据需要存入的数据大小定义不同的BLOB类型。
需要注意的是:如果存储的文件过大,数据库的性能会下降。
如果在指定了相关的Blob类型以后,还报错:xxx too large,那么在mysql的安装目录下,找my.ini文件加上如下的配置参数:
max_allowed_packet=16M。同时注意:修改了my.ini文件之后,需要重新启动mysql服务。
@Test
public void test1() ~~throws Exception~~ { //正式应该使用Try catch进行操作
Connection conn = JDBCUtils.getConnection();
String sql = "INSERT INTO customers(NAME,email,photo) VALUES(?,?,?);";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setObject(1,"Tom");
ps.setObject(2,"[email protected]");
InputStream is = new FileInputStream(new File("a1.jpeg"));
ps.setBlob(3,is);
ps.execute();
is.close();
JDBCUtils.closeResource(conn,ps);
}
@Test
public void test1() ~~throws Exception~~ { //正式应该使用Try catch进行操作
Connection conn = JDBCUtils.getConnection();
String sql = "SELECT NAME,email,photo FROM customers WHERE id = ?;";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setObject(1,27);
ResultSet rs = ps.executeQuery();
if(rs.next()){
String name = rs.getString("Name");
String email = rs.getString("email");
System.out.println("Name: " + name + "email: " + email);
//将Blob类型的数据保存到本地
Blob photo = rs.getBlob("photo");
InputStream is = photo.getBinaryStream();
FileOutputStream fis = new FileOutputStream(new File("pic.jpg"));
byte[] buffer = new byte[1024];
int len = 0;
while((len = is.read(buffer)) != -1){
fis.write(buffer,0,len);
}
fis.close();
is.close();
}
JDBCUtils.closeResource(conn,ps);
}
使用Statement进行批量添加
@Test
public void test3() throws Exception{
Connection conn = JDBCUtils.getConnection();
Statement st = conn.createStatement();
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
String sql = "INSERT INTO goods(NAME) VALUES('Jack_"+i+"')";
st.execute(sql);
}
long end = System.currentTimeMillis();
System.out.println(end-start);
JDBCUtils.closeResource(conn,st);
}
------------------
142333
使用PrepaerStatement进行批量添加
@Test
public void test4() throws Exception{
Connection conn = JDBCUtils.getConnection();
String sql = "INSERT INTO goods(NAME) VALUES(?);";
PreparedStatement ps = conn.prepareStatement(sql);
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
ps.setObject(1,"jack_"+i);
ps.execute();
}
long end = System.currentTimeMillis();
System.out.println(end-start);
JDBCUtils.closeResource(conn,ps);
}
--------------------
142134
方式一和方式二,每一条SQL都会去执行一次,效率比较慢
方式三通过将需要执行的SQL缓存下来,批量执行,提升效率
?rewriteBatchedStatements=true 写在配置文件的url后面
@Test
public void test5() throws Exception{
Connection conn = JDBCUtils.getConnection();
String sql = "INSERT INTO goods(NAME) VALUES(?)";
PreparedStatement ps = conn.prepareStatement(sql);
long start = System.currentTimeMillis();
for (int i = 1; i <= 100000; i++) {
ps.setObject(1,"jack_"+i);
//缓存SQL
ps.addBatch();
if(i%500 == 0){
//将缓存的SQL批量执行
ps.executeBatch();
//清理缓存
ps.clearBatch();
}
}
long end = System.currentTimeMillis();
System.out.println(end-start);
JDBCUtils.closeResource(conn,ps);
}
--------------------
1374
方式二插入10W数据:142134毫秒
方式三插入10W条数据:1377毫秒
方式三插入100W条数据:11602毫秒
在方式三的基础上再次进行迭代
将自动提交事务关闭,全部SQL执行完毕之后再提交
@Test
public void test6() throws Exception{
Connection conn = JDBCUtils.getConnection();
//关闭事务自动提交
conn.setAutoCommit(false);
String sql = "INSERT INTO goods(NAME) VALUES(?)";
PreparedStatement ps = conn.prepareStatement(sql);
long start = System.currentTimeMillis();
for (int i = 1; i <= 1000000; i++) {
ps.setObject(1,"jack_"+i);
ps.addBatch();
if(i%500 == 0){
ps.executeBatch();
ps.clearBatch();
}
}
//提交事务
conn.commit();
long end = System.currentTimeMillis();
System.out.println(end-start);
JDBCUtils.closeResource(conn,ps);
}
---------------------
//插入100w数据
8730
@Test
public void test3(){
Connection conn = null;
try {
//1. 获取连接
conn = JDBCUtills.getConnection();
//2. 开启事务
conn.setAutoCommit(false);
String sql1 = "UPDATE user_table SET balance = 1100 WHERE USER = ?";
//3. 进行数据库操作
updateWithTx(conn,sql1,"AA");
//模拟网络异常
System.out.println(10/0);
String sql2 = "UPDATE user_table SET balance = 900 WHERE USER = ?";
updateWithTx(conn,sql2,"BB");
//4.提交事务
conn.commit();
} catch (Exception e) {
e.printStackTrace();
try {
//5.异常回滚
conn.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
finally {
try {
//6.恢复DML自动提交,主要针对数据库连接池
conn.setAutoCommit(true);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
//7.关闭资源
JDBCUtills.closeResource(conn,null);
}
}
//通用更新操作
public void updateWithTx(Connection conn,String sql, Object... args){
PreparedStatement ps = null;
try {
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
ps.execute();
} catch (Exception e) {
e.printStackTrace();
}
finally {
JDBCUtills.closeResource(null,ps);
}
}
展示脏读/不可重复读/幻读 在JDBC中用代码如何重现,详细可以查看MYSQL笔记中事务并发补充章节
/**
* 重现脏读
* @return
*/
@Test
public void T1(){
Connection con1 = null;
List res = null;
try {
con1 = JDBCUtills.getConnection();
//设置隔离级别为read uncommitted
con1.setTransactionIsolation(1);
//开启事务
con1.setAutoCommit(false);
String sql1 = "Select * from user_table";
//读取数据
res = getInstance(con1, user_table.class, sql1);
res.forEach(System.out::println);
//T2修改表后再读取数据
res = getInstance(con1, user_table.class, sql1);
res.forEach(System.out::println);
//T2回滚后再读取数据
res = getInstance(con1, user_table.class, sql1);
res.forEach(System.out::println);
con1.commit();
} catch (Exception e) {
e.printStackTrace();
}
JDBCUtills.closeResource(con1,null);
}
@Test
public void T2(){
Connection con1 = null;
List res = null;
try {
con1 = JDBCUtills.getConnection();
con1.setTransactionIsolation(1);
con1.setAutoCommit(false);
String sql1 = "update user_table set balance = 6666 where user = 'AA'";
getUpdate(con1,sql1);
con1.rollback();
} catch (Exception e) {
e.printStackTrace();
}
JDBCUtills.closeResource(con1,null);
}
@Test
public void T1(){
Connection con1 = null;
List res = null;
try {
con1 = JDBCUtills.getConnection();
//设置隔离级别为read committed
con1.setTransactionIsolation(2);
//开启事务
con1.setAutoCommit(false);
String sql1 = "Select * from user_table";
//第一次读取数据
res = getInstance(con1, user_table.class, sql1);
res.forEach(System.out::println);
//T2提交后 T1第二次读取数据
res = getInstance(con1, user_table.class, sql1);
res.forEach(System.out::println);
con1.commit();
} catch (Exception e) {
e.printStackTrace();
}
JDBCUtills.closeResource(con1,null);
}
@Test
public void T2(){
Connection con1 = null;
List res = null;
try {
con1 = JDBCUtills.getConnection();
con1.setTransactionIsolation(2);
con1.setAutoCommit(false);
String sql1 = "update user_table set balance = 2333 where user = 'AA'";
getUpdate(con1,sql1);
con1.commit();
} catch (Exception e) {
e.printStackTrace();
}
JDBCUtills.closeResource(con1,null);
}
/**
* 模拟幻读
* @return
*/
@Test
public void T1(){
Connection con1 = null;
List res = null;
try {
con1 = JDBCUtills.getConnection();
//设置隔离级别为repeatable read
con1.setTransactionIsolation(4);
//开启事务
con1.setAutoCommit(false);
String sql1 = "Select * from user_table";
//读取数据查看数据
res = getInstance(con1, user_table.class, sql1);
res.forEach(System.out::println);
//等待T2插入数据后,进行更新操作
String sql2 = "update user_table set password = 'ccc'";
//输出受影响行数
int count = getUpdate(con1, sql2);
System.out.println(count);
con1.commit();
} catch (Exception e) {
e.printStackTrace();
}
JDBCUtills.closeResource(con1,null);
}
@Test
public void T2(){
Connection con1 = null;
List res = null;
try {
con1 = JDBCUtills.getConnection();
con1.setTransactionIsolation(4);
con1.setAutoCommit(false);
String sql1 = "insert into user_table values('FF','aaa',3000)";
getUpdate(con1,sql1);
con1.commit();
} catch (Exception e) {
e.printStackTrace();
}
JDBCUtills.closeResource(con1,null);
}
public abstract class BaseDao {
//事务通用查询
public <T> List<T> getInstance(Connection conn, Class<T> clazz, String sql, Object... args) {
List<T> list = null;
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 rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
list = new ArrayList<>();
while (rs.next()) {
T t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
String columnLabel = rsmd.getColumnLabel(i + 1);
Object value = rs.getObject(i + 1);
//反射类所有字段,并且赋值
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, value);
}
list.add(t);
}
} catch (Exception throwables) {
throwables.printStackTrace();
} finally {
JDBCUtills.closeResource(null, ps, rs);
}
return list;
}
//事务通用更新
public <T> int getUpdate(Connection conn, String sql, Object... args) {
List<T> list = null;
PreparedStatement ps = null;
int row = -1;
try {
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
row = ps.executeUpdate();
} catch (Exception throwables) {
throwables.printStackTrace();
} finally {
JDBCUtills.closeResource(null, ps);
return row;
}
}
public <T> T getValue(Connection conn, String sql, Object... args) {
Object val = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(sql);
val = null;
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
rs = ps.executeQuery();
if (rs.next()) {
val = rs.getObject(1);
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
JDBCUtills.closeResource(null, ps, rs);
}
return (T) val;
}
}
public interface CustomerDao {
/**
* 将cust对象添加到数据库之中
* @param conn
* @param cust
*/
void insert(Connection conn, Customer cust);
/**
* 根据id号删除数据库中customer数据
* @param conn
* @param id
*/
void deleteById(Connection conn, int id);
/**
* 根据id号修改数据库中customer数据
* @param conn
* @param cust
* @param id
*/
void updateById(Connection conn,Customer cust, int id);
/**
* 根据id号查詢数据库中customer数据
* @param conn
* @param id
*/
Customer getCustomerById(Connection conn, int id);
/**
* 查询数据库customer表中所有的数据
* @param conn
*/
List getAll(Connection conn);
/**
* 查询数据库customer表中所有的行数
* @param conn
*/
long getCount(Connection conn);
/**
* 查询数据库customer表中年龄最大的生日
* @param conn
*/
Date getMaxBirth(Connection conn);
}
public class CustomerDaoImpl extends BaseDao implements CustomerDao{
@Override
public void insert(Connection conn, Customer cust) {
String sql = "insert into customers(name,email,birth) values(?,?,?)";
getUpdate(conn,sql,cust.getName(),cust.getEmial(),cust.getBirth());
}
@Override
public void deleteById(Connection conn, int id) {
String sql = "delete from customers where id = ?";
getUpdate(conn,sql,id);
}
@Override
public void updateById(Connection conn, Customer cust, int id) {
String sql = "update customers set name = ?, email = ?, birth = ? where id = ?";
getUpdate(conn,sql,cust.getName(),cust.getEmial(),cust.getBirth(),id);
}
@Override
public Customer getCustomerById(Connection conn, int id) {
String sql = "select name, email, birth from customers where id = ?";
List list = getInstance(conn, Customer.class, sql, id);
return list.size() > 0 ? list.get(0) :null;
}
@Override
public List getAll(Connection conn) {
String sql = "select name, email, birth from customers";
List list = getInstance(conn, Customer.class, sql);
return list;
}
@Override
public long getCount(Connection conn) {
String sql = "select count(*) from customers";
return getValue(conn,sql);
}
@Override
public Date getMaxBirth(Connection conn) {
String sql = "select max(birth) from customers";
return getValue(conn,sql);
}
}
Ctril + Shift + T 创建实现类的单元测试类
public class CustomerDaoImplTest {
CustomerDaoImpl custImpl = new CustomerDaoImpl();
@Test
public void insert() {
Connection conn = null;
try {
conn = JDBCUtills.getConnection();
Customer customer = new Customer("jack","[email protected]",new Date(543534543534L));
custImpl.insert(conn,customer);
System.out.println("添加成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtills.closeResource(conn,null);
}
}
@Test
public void deleteById() {
Connection conn = null;
try {
conn = JDBCUtills.getConnection();
custImpl.deleteById(conn,19);
System.out.println("删除成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtills.closeResource(conn,null);
}
}
@Test
public void updateById() {
Connection conn = null;
try {
conn = JDBCUtills.getConnection();
Customer customer = new Customer("jack.ma","[email protected]",new Date(543534543534L));
custImpl.updateById(conn,customer,20);
System.out.println("更新成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtills.closeResource(conn,null);
}
}
@Test
public void getCustomerById() {
Connection conn = null;
try {
conn = JDBCUtills.getConnection();
Customer cust = custImpl.getCustomerById(conn, 20);
System.out.println(cust);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtills.closeResource(conn,null);
}
}
@Test
public void getAll() {
Connection conn = null;
try {
conn = JDBCUtills.getConnection();
List list = custImpl.getAll(conn);
list.forEach(System.out::println);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtills.closeResource(conn,null);
}
}
@Test
public void getCount() {
Connection conn = null;
try {
conn = JDBCUtills.getConnection();
long count = custImpl.getCount(conn);
System.out.println(count);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtills.closeResource(conn,null);
}
}
@Test
public void getMaxBirth() {
Connection conn = null;
try {
conn = JDBCUtills.getConnection();
Date birdth = custImpl.getMaxBirth(conn);
System.out.println(birdth);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtills.closeResource(conn,null);
}
}
}
优化前:BaseDao类中,需要传入具体的类型的Class对象
//未优化:需要传入clazz
public List getInstance(Connection conn, Class clazz, String sql, Object... args) {
优化后:BaseDao类中,不需要传入Class对象
//添加泛型类
public abstract class BaseDao<T> {
//添加泛型类型变量
private Class<T> clazz = null;
//反射获取泛型类型
public BaseDao(){
Class clazz = this.getClass();
//getGenericSuperclass 获取当前带泛型的父类
Type genericSuperclass = clazz.getGenericSuperclass();
System.out.println(genericSuperclass);
//getActualTypeArguments 获取当前带泛型父类的泛型
ParameterizedType paraType = (ParameterizedType) genericSuperclass;
Type[] arguments = paraType.getActualTypeArguments();
//获取泛型类型
this.clazz = (Class) arguments[0];
}
}
T t = clazz.newInstance();
总结:使用属性Class进行实例化,不再方法调用时进行类型传递
BaseDao:实现对表的通用操作:通用增删改查
Dao接口: 定于对于某个表的的抽象方法
Dao接口实现类:实现接口中的抽象方法
JDBC数据库连接池的必要性
数据库连接池技术
数据库连接池技术的优点
1. 资源重用
由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。
2. 更快的系统反应速度
数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间
3. 新的资源分配手段
对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源
4. 统一的连接管理,避免数据库连接泄漏
在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露
多种开源的数据库连接池
连接方式1:不推荐
@Test
public void test1() throws Exception {
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass( "com.mysql.cj.jdbc.Driver" ); //loads the jdbc driver
cpds.setJdbcUrl( "jdbc:mysql://localhost:3306/test" );
cpds.setUser("root");
cpds.setPassword("123");
//设置数据库连接池
//设置初始化数据库连接池的连接数
cpds.setInitialPoolSize(10);
Connection conn = cpds.getConnection();
System.out.println(conn);
//销毁c3p0连接池,一般不销毁
//DataSources.destroy( cpds );
}
连接方式2:推荐 使用xml配置文件连接
c3p0-config.xml
<c3p0-config>
<named-config name="helloC3po">
<property name="user">rootproperty>
<property name="password">123property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/testproperty>
<property name="driverClass">com.mysql.cj.jdbc.Driverproperty>
<property name="acquireIncrement">5property>
<property name="initialPoolSize">5property>
<property name="minPoolSize">5property>
<property name="maxPoolSize">10property>
<property name="maxStatements">20property>
<property name="maxStatementsPerConnection">5property>
named-config>
c3p0-config>
//数据库连接池-饿汉式
private static ComboPooledDataSource cpds = new ComboPooledDataSource("helloC3po");
//返回连接
public static Connection getConnection() throws SQLException {
Connection conn = cpds.getConnection();
return conn;
}
方式一:硬编码,不推荐
@Test
public void test1() throws SQLException {
//创建dbcp的数据库连接池
BasicDataSource source = new BasicDataSource();
source.setDriverClassName("com.mysql.cj.jdbc.Driver");
source.setUsername("root");
source.setPassword("123");
source.setUrl("jdbc:mysql://localhost:3306/test");
//设置数据库连接池
source.setInitialSize(10);
source.setMaxActive(10);
Connection conn = source.getConnection();
System.out.println(conn);
}
方式二:配置文件,推荐
@Test
public void test2() throws Exception {
//读取properties配置文件
InputStream is = ClassLoader.getSystemResourceAsStream("dbcp2.properties");
Properties info = new Properties();
info.load(is);
//创建dbcp的数据库连接池
DataSource source = BasicDataSourceFactory.createDataSource(info);
Connection conn = source.getConnection();
System.out.println(conn);
}
Druid详细配置
@Test
public void getConnection() throws Exception {
DruidDataSource source = new DruidDataSource();
//硬编码加载
source.setUsername("root");
source.setPassword("123");
source.setUrl("jdbc:mysql://localhost:3306/test");
source.setDriverClassName("com.mysql.cj.jdbc.Driver");
//多态调用连接方法
DataSource s1 = source;
Connection conn = s1.getConnection();
System.out.println(conn);
}
@Test
public void getConnection() throws Exception {
Properties info = new Properties();
//类加载器
InputStream is = ClassLoader.getSystemResourceAsStream("jdbc.properties");
info.load(is);
DataSource source = DruidDataSourceFactory.createDataSource(info);
Connection conn = source.getConnection();
System.out.println(conn);
}
使用PreparedStatement对CURD进行封装,底层一致,对比自己写的健壮性更好一点
@Test
public void addTest() throws Exception {
//Druid连接池
Connection conn = source.getConnection();
//DBUtills
QueryRunner runner = new QueryRunner();
String sql = "insert into user_table values(?,?,?)";
int res = runner.update(conn, sql, "jack1", "123", 2000);
System.out.println("insert successed");
}
查询单条记录
/*
* 测试查询:查询一条记录
*
* 使用ResultSetHandler的实现类:BeanHandler
*/
@Test
public void querySingleTest() throws Exception {
//Druid连接池
Connection conn = source.getConnection();
//DBUtills
QueryRunner runner = new QueryRunner();
//查询单条结果
String sql = "SELECT * FROM customers WHERE id = ?";
BeanHandler<Customer> handler = new BeanHandler(Customer.class);
Customer customer = runner.query(conn, sql, handler, 1);
System.out.println(customer);
//释放连接到资源池
JDBCUtills.closeResource(conn,null);
}
查询多条记录
/*
* 测试查询:查询多条记录构成的集合
*
* 使用ResultSetHandler的实现类:BeanListHandler
*/
@Test
public void queryMultipleTest() throws Exception {
//Druid连接池
Connection conn = source.getConnection();
//DBUtills
QueryRunner runner = new QueryRunner();
//查询多条结果集
String sql2 = "SELECT * FROM customers";
BeanListHandler<Customer> listHandler = new BeanListHandler(Customer.class);
List<Customer> list = runner.query(conn, sql2, listHandler);
list.forEach(System.out::println);
//释放连接到资源池
JDBCUtills.closeResource(conn,null);
}
查询单个值
/*
* 测试查询:查询单个值
*
* 使用ResultSetHandler的实现类:ScalarHandler
*/
@Test
public void queryValueTest() throws Exception {
//Druid连接池
Connection conn = source.getConnection();
//DBUtills
QueryRunner runner = new QueryRunner();
//查询单条结果
String sql = "SELECT Count(*) FROM customers";
ScalarHandler handler = new ScalarHandler();
long o = (long) runner.query(conn, sql, handler);
System.out.println(o);
//释放连接到资源池
JDBCUtills.closeResource(conn,null);
}
@Test
//自定义resultHandler
public void test1() throws SQLException {
//Druid连接池
Connection conn = source.getConnection();
//DBUtills
QueryRunner runner = new QueryRunner();
//查询单条结果
String sql = "SELECT * FROM customers where id = 1";
ResultSetHandler<Customer> selfDefineHandler = new ResultSetHandler<Customer>() {
@Override
public Customer handle(ResultSet resultSet) throws SQLException {
ResultSetMetaData rsmd = resultSet.getMetaData();
int columnCount = rsmd.getColumnCount();
if(resultSet.next()){
Customer cust = new Customer();
for (int i = 0; i < columnCount; i++) {
String columnLabel = rsmd.getColumnLabel(i + 1);
Object value = resultSet.getObject(i + 1);
try {
Field field = Customer.class.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(cust,value);
catch (Exception e) {
e.printStackTrace();
}
finally {
if(resultSet!=null)
resultSet.close();
}
}
return cust;
}
return null;
}
};
Customer query = runner.query(conn, sql, selfDefineHandler);
System.out.println(query);
//释放连接到资源池
JDBCUtills.closeResource(conn,null);
}
public void test1(Connection con, Statement st, ResultSet rs){
//方式1
try {
DbUtils.close(con);
DbUtils.close(st);
DbUtils.close(rs);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
//方式2
DbUtils.closeQuietly(con);
DbUtils.closeQuietly(st);
DbUtils.closeQuietly(rs);
}
经过系统的学习JDBC核心技术,以下为个人总结的JDBC核心知识点,总体概括。
概念理解
环境准备
CURD
事务
#1关闭自动提交事务
SET autocommit = 0;
#2开始事务
START TRANSACTION;
#3 编写一组事务的语句【select、insert、update、delete】
语句1
语句2
...
语句3
#4结束事务
commit; #提交
rollback; #回滚
Dao
对数据库的CURD,不包含具体的业务信息,优点是实现了功能的模块化,有利于代码维护和升级
数据库连接池
每次数据库请求连接,关闭连接都很耗费时间,所以出现了数据库连接池技术,一次性开辟指定连接,每次请求都从连接池中获取,释放连接,实际上是释放到数据库连接池,而不是将连接关闭,实现对数据库连接的管理以及提升运行效率。
Apache-DBUtils
使用PreparedStatement对CURD进行封装,底层一致,对比自己写的健壮性更好一点
由于对于数据的增删改查操作非常频繁,所以官方将这些比较通用的方法封装成了一个工具类,供我们使用,底层方法其实和例子中的是一致的,健壮性更好些而已。