JDBC(Java database connectivity)
1. JDBC简介
-
独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口(一组API),定义了用来访问数据库的标准Java类库,(java.sql,javax.sql)使用这些类库可以以一种标准的方法、方便地访问数据库资源。
-
JDBC为访问不同的数据库提供了一种统一的途径,为开发者屏蔽了一些细节问题。
-
JDBC的目标是使Java程序员使用JDBC可以连接任何提供了JDBC驱动程序
2. 数据库的连接方式举例:
package www.fancy.connection;
import org.junit.Test;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
/**
* @Name
* @Description 获取数据库连接方式一
* @Author Fancy
* @return 2022/3/23
* @Version 1.0
*/
public class ConnectionTest {
@Test
public void testConnection1() throws SQLException {
Driver driver = new com.mysql.jdbc.Driver();
String url = "jdbc:mysql://localhost:3306/test";
//- **jdbc:子协议:子名称**
//- **协议**:JDBC URL中的协议总是jdbc
//- **子协议**:子协议用于标识一个数据库驱动程序
//- **子名称**:一种标识数据库的方法。子名称可以依不同的子协议而变化,用子名称的目的是为了**定位数据库**提供足够的信息。包含**主机名**(对应服务端的ip地址)**,端口号,数据库名**
Properties info =new Properties();
info.setProperty("user","localhost");
info.setProperty("password","123456");
//获取了数据库的连接
Connection conn = driver.connect(url,info);
System.out.println(conn);
}
/**
* @Name
* @Description 获取数据库连接方式二
* @Author Fancy
* @return 2022/3/23
* @Version 1.0
*/
@Test
public void testConnection2() throws SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
//使用反射,获取Driver类的对象
Class clazz = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) clazz.newInstance();
String url = "jdbc:mysql://localhost:3306/test";
//- **jdbc:子协议:子名称**
//- **协议**:JDBC URL中的协议总是jdbc
//- **子协议**:子协议用于标识一个数据库驱动程序
//- **子名称**:一种标识数据库的方法。子名称可以依不同的子协议而变化,用子名称的目的是为了**定位数据库**提供足够的信息。包含**主机名**(对应服务端的ip地址)**,端口号,数据库名**
Properties info =new Properties();
info.setProperty("user","localhost");
info.setProperty("password","123456");
//获取了数据库的连接
Connection conn = driver.connect(url,info);
System.out.println(conn);
}
/**
* @Name
* @Description 获取数据库连接方式三:DriverManager
* @Author Fancy
* @return 2022/3/23
* @Version 1.0
*/
@Test
public void testConnection3() throws SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
String url = "jdbc:mysql://localhost:3306/test";
String user = "localhost";
String password = "123456";
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection(url, user, password);
}
/**
* @Name
* @Descriptio 获取数据库连接方式四:将数据库连接需要的4个基本信息声明在配置文件中,通过读取配置文件的方式,获取连接
* 实现了数据与代码的分离;
* 如果需要修改配置文件信息,可以避免程序重新打包
* @Author Fancy
* @return 2022/3/23
* @Version 1.0
*/
@Test
public void testConnection4() throws Exception{
//1.读取配置文件中的4个基本信息
InputStream in = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties pros = new Properties();
//加载文件
pros.load(in);
String user = pros.getProperty("user");
String password = pros.getProperty("password");
String url = pros.getProperty("url");
String driverClass = pros.getProperty("driverClass");
//2.加载驱动
Class.forName(driverClass);
//3.获取连接
Connection conn = DriverManager.getConnection(url, user, password);
System.out.println(conn);
}
}
3.
package www.fancy.connection;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
/**
* @Name mysqlOutputTest
* @Description 获取数据库连接’tdkaoqin‘-’sample‘数据库-’sample‘,做查询操作。
* @Author Fancy
* @return 2022/3/24
* @Version 1.0
*/
public class mysqlOutputTest {
public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException {
//1.获取数据库的连接
//1.1读取配置文件’jdbc.properties‘
//将配置文件变为字节输入流加载到当前类中
InputStream in = mysqlOutputTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
//创建一个持久的属性集
Properties pro = new Properties();
//从输入流in中读取属性列表(键和元素对)
pro.load(in);
//读取连接数据库所需的属性,根据key读取value
String user = pro.getProperty("user");//获取用户名
String password = pro.getProperty("password");//获取密码
String url = pro.getProperty("url");//获取mysql被注册的驱动程序的标识
String driverClass = pro.getProperty("driverClass");//获取mysql的驱动
//2.加载驱动,实例化Driver
Class.forName(driverClass);
//3.使用DriverManager来注册驱动(静态代码块中,会调用 DriverManager.registerDriver() 方法来注册自身的一个实例)
Connection conn = DriverManager.getConnection(url, user, password);//此时已成功建立了java程序和’tdkaoqin‘-’sample‘数据库-’sample‘表
//4.操作和访问数据库
//预编译sql语句,返回prepareStatement的实例
//String sql = "SELECT * FROM ( SELECT P1.`样品名称`,P1.`样品批号`,P1.`检测项目`,P1.`样品储存条件`,P1.`样品含量范围`,P1.`样品成分性质`,P1.`是否含有干扰检测的物质`,P1.`样品前处理方式`,P1.`对应送样单编号`,P3.`项目编号`,P2.`备注` FROM((SELECT `样品名称`,`样品批号`,`检测项目`,`样品储存条件`,`样品含量范围`,`样品成分性质`,`是否含有干扰检测的物质`,`样品前处理方式`,`对应送样单编号` FROM sample WHERE `样品批号` IS NOT NULL) P1 LEFT JOIN (SELECT `备注`,`对应送样单编号` FROM sample WHERE `备注` IS NOT NULL) P2 ON P1.`对应送样单编号` = P2.`对应送样单编号`) LEFT JOIN (SELECT `项目编号`,`对应送样单编号` FROM sample WHERE `项目编号` IS NOT NULL) P3 ON P3.`对应送样单编号`= P2.`对应送样单编号`) A WHERE A.`检测项目` LIKE('纯度%') AND A.`项目编号` LIKE('60%') ";
String sql = "select "+"`name`"+",`price` FROM districtproducts WHERE "+"`price`"+" LIKE ('3%')";
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery(sql);
while (rs.next()){
System.out.print(rs.getString("name")+",");
System.out.println(rs.getString("price"));
}
}
}
封装数据库的连接、资源关闭的方法,为JDBC工具类
package www.fancy.connection;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* @Name connection
* @Description 获取数据库的连接
* @Author Fancy
* @return 2022/3/24
* @Version 1.0
*/
public class JDBCUtils {
public static Connection getConnection() throws Exception {
//1.获取数据库的连接
//1.1读取配置文件’jdbc.properties‘
//将配置文件变为字节输入流加载到当前类中
InputStream in = mysqlOutputTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
//创建一个持久的属性集
Properties pro = new Properties();
//从输入流in中读取属性列表(键和元素对)
pro.load(in);
//读取连接数据库所需的属性,根据key读取value
String user = pro.getProperty("user");//获取用户名
String password = pro.getProperty("password");//获取密码
String url = pro.getProperty("url");//获取mysql被注册的驱动程序的标识
String driverClass = pro.getProperty("driverClass");//获取mysql的驱动
//2.加载驱动,实例化Driver
Class.forName(driverClass);
//3.使用DriverManager来注册驱动(静态代码块中,会调用 DriverManager.registerDriver() 方法来注册自身的一个实例)
Connection conn = DriverManager.getConnection(url, user, password);//此时已成功建立了java程序和’tdkaoqin‘-’sample‘数据库-’sample‘表
return conn;
}
/**
* @Name closeResource
* @Description 关闭资源的操作(连接statement)
* @Author Fancy
* @return 2022/3/24
* @Version 1.0
*/
public void closeResource(Connection conn, Statement ps){
try {
if (conn != null)
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (ps != null)
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
-
-
-
面向接口编程的思想
-
ORM思想(object relational mapping)
-
一个数据表对应一个java类
-
表中的一条记录对应java类的一个对象
-
表中的一个字段对应java类的一个属性
-
sql是需要结合列名和表的属性名来写。注意起别名。
-
-
两种技术
-
JDBC结果集的元数据:ResultSetMetaData
-
获取列数:getColumnCount()
-
获取列的别名:getColumnLabel()
-
-
JDBC事务处理
什么是事务?
-
-
事务处理(事务操作):保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态。
-
为确保数据库中数据的一致性
举例说明
package www.fancy.connection;
import org.junit.Test;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
public class transactionTest {
@Test
public void Test1() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
//1.取消数据的自动提交
conn.setAutoCommit(false);
String sql1 = "Update addresses set family_id=family_id-101 WHERE family_id = ?";
update(conn, sql1, 400);
//System.out.println(10/0);
String sql2 = "Update addresses set family_id=family_id+100 WHERE family_id = ?";
update(conn, sql2, 300);
System.out.println("successful");
//2.提交数据
conn.commit();
} catch (Exception e) {
e.printStackTrace();
//3.回滚数据
try {
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
} finally {
//使用数据库连接池时,要修改回自动提交数据
try {
conn.setAutoCommit(true);
} catch (SQLException e) {
e.printStackTrace();
}
JDBCUtils.closeResource(conn, null);
}
}
//通用的增、删、改操作(体现一:增、删、改 ; 体现二:针对于不同的表)
public void update(String sql, Object... args) {
Connection conn = null;
PreparedStatement ps = null;
try {
//1.获取数据库的连接
conn = JDBCUtils.getConnection();
//2.获取PreparedStatement的实例 (或:预编译sql语句)
ps = conn.prepareStatement(sql);
//3.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
//4.执行sql语句
ps.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
//5.关闭资源
JDBCUtils.closeResource(conn, ps);
}
}
public int update(Connection con, String sql, Object... args) {
Connection conn = null;
PreparedStatement ps = null;
try {
//1.获取PreparedStatement的实例 (或:预编译sql语句)
ps = conn.prepareStatement(sql);
//2.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
//3.执行sql语句
ps.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
//4.关闭资源
JDBCUtils.closeResource(null, ps);
}
return 0;
}
//隔离级别
@Test
public void testTransaction2() throws Exception {
Connection conn = JDBCUtils.getConnection();
String sql = "update addresses ";
testQuery4(conn,Addresses.class,sql,999);
}
/**
* @Name testQuery4
* @Description 针对于不同表的通用查询操作,泛型方法,考虑事务
* @Author Fancy
* @return 2022/3/25
* @Version 1.0
*/
public <T> ArrayList<T> testQuery4(Connection conn,Class<T> clazz, String sql, Object...args) {
PreparedStatement ps = null;
ResultSet rs = null;
try {
//获取数据库fengfeng的连接
ps = conn.prepareStatement(sql);
//填充占位符,查询小于101的数据
//ps.setObject(1,101);
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//获取结果集
//在此PreparedStatement对象中执行SQL查询,并返回查询生成的ResultSet对象
rs = ps.executeQuery();
//获取结果集的元数据(修饰结果集的数据)
ResultSetMetaData rsmd = rs.getMetaData();
ArrayList arr = new ArrayList();
//获取列数
int columnCount = rsmd.getColumnCount();
int j = 0;
while (rs.next()){
//通过反射造T对象
arr.add(clazz.newInstance());
for (int i = 0; i < columnCount; i++) {
//获取每一列的列值
Object columnValue = rs.getObject(i+1);
/*//获取每一列的列名
String columnName = rsmd.getColumnName(i+1);
这样写会报错因为mysql中的列名和java类中的属性名不一致
导致报错:java.lang.NoSuchFieldException: family_id
,我们查询时应当使用别名,别名和类的属性名一直
并且方法换为getColumnNamelabel*/
String columnName = rsmd.getColumnLabel(i + 1);
//通过反射,将对象指定名columnName的属性赋值为指定的值columnValue
/* 返回一个Field对象,该对象反映由该类对象表示的类或接口的指定声明字段。name参数是一个字符串,用于指定所需字段的简单名称。
如果这个类对象代表数组类型,那么这个方法找不到数组类型的长度字段。
参数:name–字段的名称
返回:此类中指定字段的字段对象*/
Field field = clazz.getDeclaredField(columnName);
//将该field可访问
field.setAccessible(true);
//赋值
field.set(arr.get(j),columnValue);
}
j++;
}
return arr;
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭资源
JDBCUtils.closeResource(null,ps,rs);
}
return null;
}
}
数据库的并发问题
-
-
脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。
-
不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字段, 值就不同了。
-
幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。
-
-
数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题。
-
一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度,
-
-
SELECT @@tx_isolation;
-
设置当前 mySQL 连接的隔离级别:
set transaction isolation level read committed;
-
设置数据库系统的全局的隔离级别:
set global transaction isolation level read committed;
-
补充操作:
-
创建mysql数据库用户:
create user tom identified by 'abc123';
-
授予权限
#授予通过网络方式登录的tom用户,对所有库所有表的全部权限,密码设为abc123. grant all privileges on *.* to tom@'%' identified by 'abc123'; #给tom用户使用本地命令行方式,授予atguigudb这个库下的所有表的插删改查的权限。 grant select,insert,delete,update on atguigudb.* to tom@localhost identified by 'abc123';
-
-
定义及作用
层次结构图
数据库连接池
以上模式存在的问题
1.数据库的连接资源并没有得到很好的重复利用
2.对于每一次数据连接,使用完后都得断开
3.这种开发不能控制被创建的连接对象数
数据库连接池:
基本思想:为数据库建立一个“缓冲池”、需要时取出一个,使用完毕后再放回去
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据连接,而不是重新建立一个
数据库连接的数量是由最小数据库连接数来设定的,连接池的最大数据库连接数量限定了这个连接池占有的最大连接数
多种开源的数据库连接池
DBCP/C3P0/Druid
package www.fancy.connectionpool;
import com.mchange.v2.c3p0.*;
import org.junit.Test;
public class PoolTest {
@Test
public void testGetConnection() throws Exception{
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass( "com.mysql.jdbc.Driver" );//loads the jdbc driver
cpds.setJdbcUrl( "jdbc:mysql://localhost:3306/fengfeng" );
cpds.setUser("localhost"); cpds.setPassword("123456");
//通过设置参数,对数据库连接池进行管理
//设置初始时数据库连接池中的连接数
cpds.setInitialPoolSize(10);
System.out.println(cpds.getConnection());
}
}