JDBC
为多种关系型数据库提供统一的访问,由一组用Java语言编写的类和接口组成。
JDBC快速入门
1、导入jar包
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
2、注册驱动程序
//1,获取Driver的实现类
Class clazz = Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver = (Driver)clazz.getDeclaredConstructor().newInstance();
//2,注册驱动
DriverManager.registerDriver(driver);
3、获取数据库连接
//3,提供url,用户名,密码
String url = "";
String user = "";
String password = "";
//4,获取连接
Connection connection = DriverManager.getConnection(url, user, password);
4、获取执行者对象
5、执行Sql语句并返回结果
6、处理结果
7、释放资源
操作和访问数据库
·数据库连接被用于向数据库服务器发送命令和SQL语句,并接受数据库服务器返回的结果。其实一个数据库连接就是一个Socket连接
·在Java.sql包中有三个接口分别定义了对数据库的调用的不同方式
·Statement:用于执行静态SQL语句并返回它所生成结果的对象。
·PreparedStatement:SQL语句被预编译并存储在此对象中,可以使用此对象高效地执行该语句。
·CallableStatement:用于执行Sql存储过程
Statement操作数据库弊端
需要拼写sql语句, 存在sql注入问题
避免Sql注入,用PreparedStatement(从Statement)取代Statement
PreparedStatement(Statement子接口)
添加操作
public class PreparedStatementUpdateTest {
//向customer中添加一条记录
@Test
public void testInsert() throws IOException, ClassNotFoundException, SQLException, ParseException {
Connection connection = null;
PreparedStatement preparedStatement = null;
try{
//读取配置文件
Properties properties = new Properties();
properties.load(this.getClass().getClassLoader().getResourceAsStream("jdbc.properties"));
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driver = properties.getProperty("driver");
//加载并注册驱动
Class.forName(driver);
//获取连接
connection = DriverManager.getConnection(url, user, password);
if(connection != null ) System.out.println("连接成功");
else System.out.println("连接失败");
//获取preparedStatement对象
String sql = "insert into customers(name,email,birth) values(?,?,?)"; // ? 是占位符
preparedStatement = connection.prepareStatement(sql);
//填充占位符
preparedStatement.setString(1,"哪吒");
preparedStatement.setString(2,"nezha@163.com");
//规定格式
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
//将字符串转换为日期
java.util.Date date = simpleDateFormat.parse("1000-01-01");
//获取日期的毫秒数
preparedStatement.setDate(3,new Date(date.getTime()));
//将java.util.Date的毫秒数转换成sql下的date
//执行sql
preparedStatement.execute();
}
catch(Exception e){
e.printStackTrace();
}finally {
//资源关闭
try {
if (preparedStatement != null )
preparedStatement.close();
}catch (SQLException s){
s.printStackTrace();
}
try{
if (connection != null )
connection.close();
}catch (SQLException s){
s.printStackTrace();
}
}
}
数据库中索引从1开始
通用修改操作
public void update(String sql , Object...args) {
Connection connection = null ;
PreparedStatement preparedStatement = null;
try{
//获取连接
Properties properties = new Properties();
properties.load(this.getClass().getClassLoader().getResourceAsStream("Jdbc.properties"));
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driver = properties.getProperty("driver");
//加载类获取对象,driver中静态方法在加载类时被执行,注册驱动
Class.forName(driver);
connection = DriverManager.getConnection(url,user,password);
if(connection != null ) System.out.println("连接成功");
else System.out.println("连接失败");
//预编译SQL语句
preparedStatement = connection.prepareStatement(sql);
//填充占位符
for(int i = 1 ; i <= args.length ; i++){
preparedStatement.setObject(i , args[i-1]);
}
//执行
preparedStatement.execute();
}catch (Exception e){
e.printStackTrace();
}finally {
//关闭资源
try{
preparedStatement.close();
}catch (Exception e){
e.printStackTrace();
}
try{
connection.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
查询操作
查询操作中需要通过preparedStatement中的executeQuery()方法来执行预编译的Sql语句,并且会返回一个ResultSet结果集来展示查询的结果,
在查询操作中,需要通过ResultSet中的getMetaData()方法获取元数据,其中包含了查询结果的列名和列数用于之后的展示查询结果的操作,可以通过反射将查询的结果赋值给java中对应的类。通过泛型方法来实现通用表查询操作
public <T>T getInstance(Class<T> clazz, String sql , Object...args){
Connection connection = null;
PreparedStatement ps = null;
ResultSet resultSet = null;
try {
connection = JdbcUtils.getConnection();
ps = connection.prepareStatement(sql);
for (int i =0 ; i < args.length ; i++){
ps.setObject(i+1,args[i]);
}
resultSet = ps.executeQuery();
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
if (resultSet.next()){
T t = clazz.getDeclaredConstructor().newInstance();
for (int i = 1 ; i <= columnCount ; i++){
Object columnValue = resultSet.getObject(i);
String columnLabel = metaData.getColumnLabel(i);
Field fieldield = clazz.getDeclaredField(columnLabel);
fieldield.setAccessible(true);
fieldield.set(t,columnValue);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtils.Close3(connection,ps,resultSet);
}
return null;
}
String
getColumnLabel(int column)
throws SQLException
获取用于打印输出和显示的指定列的建议标题。建议标题通常由 SQL AS 子句来指定。如果未指定 SQL AS,则从 getColumnLabel 返回的值将和 getColumnName 方法返回的值相同
将Blob类型数据插入表中:
通过PreparedStatement中的SetBlob(columnCount,inputStream)方法,将Blob数据填入数据库表中。
从数据库表中读取Blob数据:
通过获取结果集ResultSet之后,通过其getBlob(columnIndex)方法获取
TinyBlob 255B
Blob 65KB
MediumBlob 16MB
LongBlob 4GB
通过Blob.getBinaryStream()获取Blob的输入流。
如果在指定了相关Blob类型后,报错xxx too large ,则在Mysql的安装目录下,找到my.ini文件上添加如下的配置参数: max_allowed_packet=16M。同时注意:修改了my.ini文件之后,需要重启mysql服务。
批量插入数据的操作:
PreparedStatement的方法:
addBatch()、executeBatch()、clearBatch()
1、攒Sql
ps.addBatch();
2、执行
ps.executeBatch();
3、清空batch()
ps.clearBatch();
注:mysql服务器默认关闭批处理,需通过参数让Mysql开启批处理支持,将 ?rewriteBatchedStatements=true 写在配置文件的url后面(驱动:mysql-connector-java-51.37-bin.jar)
数据库事务
1、事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。
>一组逻辑操作单元:一个或多个DML操作
2、事务处理原则:保证所有事务都作为一个工作单元来执行,即使出现了故障都不能改变这种执行方。
当在一个事务中执行多个操作时,要么所有事物都**被提交(commit),这些修改就永久地保存下来;
要么数据库管理系统将放弃所作的所有修改,整个事务**回滚(rollback)**到最初状态。
3、数据一旦提交,就不可以回滚
4、哪些操作会导致数据自动提交
>DDL操作一旦执行,就会自动提交
>DML操作默认情况下,一旦执行,自动提交。
>可以通过set autocommit = false 的方式取消DML操作的自动提交
>默认在关闭连接时,会自动提交数据。
>如果关闭自动提交,关闭连接会自动回滚
if (!this.isClosed()) {
this.session.setForceClosedReason(reason);
try {
if (!skipLocalTeardown) {
if (!this.getAutoCommit() && issueRollback) {
try {
this.rollback();
} catch (SQLException var14) {
sqlEx = var14;
}
}
java代码取消自动提交数据
Connection.setAutoCommit(false);
JDBC程序中为了让多个SQL语句作为一个事务执行:
1、调用Connection对象的SetAutoCommit(false);已取消自动提交事务
2、在所有Sql语句都执行成功后,调用commit()方法;提交事务
3再出现异常时,调用rollback();方法回滚事务
(若此时Connection没有被关闭,还可能被重复使用,则需要恢复其自动提交状态setAutoCommit(true)。尤其是在使用数据库连接池技术时,执行close()方法前,建议回复自动提交状态)
事务的ACID属性
1、原子性(Atomicity)
原子性是指是五十一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生
2、一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另一个一致性状态
3、隔离性(Isolation)
事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事物内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰
4、持久性(Durability)
是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响
数据库的并发问题
对同时运行的多个事务,当这些事务访问数据库中相同的数据是,如果没有采取必要的隔离机制,就会导致各种并发问题
·脏读:对于两个事务T1,T2,T1读取了已经被T2更新但还没有被提交的字段,之后如果T2回滚,T1所读取的内容就是临时且无效的
·不可重复读:对于两个事务T1,T2,T1读取了一个字段,然后T2更新了该字段。之后T1再次读取同一个字段,值就不相同了。
·幻读:对于两个事务T1,T2,T1从一个表中读取了一个字段,然后T2在表中插入了一些新的行。之后,如果T1再次读取同一个表,就会多出几行。
数据库事务的隔离性:数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。
一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别,不同级别对应不同的干扰程度,
隔离级别越高,数据的一致性就越好,但并发性越强。
四种隔离级别
隔离级别
|
描述
|
READ UNCOMMITTED
(读未提交)
|
允许事务读取未被其他事务提交的变更。脏读,不可重复读,幻读等问题都会出现
|
READ COMMITTED
(读已提交)
|
只允许事务读取已经被其它事务提交的变更,可避免脏读、但不可重复读和幻读问题仍然可能出现。
|
REPEATABLE READ
(可重复读)
|
确保事务可以多次从一个字段中读取相同的值,在这个事务持续期间,禁止其他事物对这个字段进行更新,可以避免脏读、不可重复读,但幻读的问题仍然存在
|
SERIALIZABLE
(串行化)
|
确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新、删除操作,所有并发问题都可以避免,但性能十分低下。
|
Oracle支持的两种事务隔离级别:READ COMMITTED ,SERIALIZABLE. Oracle默认的事务隔离级别:READ COMMITTED
在命令行查询隔离级别:
select @@transaction_isolation
设置全局变量改变隔离级别
set global transaction isolation level 隔离级别
需要重新连接
控制台连接mysql : mysql -u root -p 密码
控制台创建用户:create user 用户名 Identifted by
‘密码
’ ;
授予用户权限:grant 权限 on 数据库.数据表 to '用户'@'%';
在java代码中通过Connection对象中的方法getTransactionIsolation()方法获取事物的隔离级别,通过setTransactionIsolation()设置事务隔离级别;
获取隔离级别
conn.getTransactionIsolation()
设置隔离级别
conn.setTransactionIsolation(1) //
DAO及其实现类
·DAO: Data Access Object 访问数据信息的类和接口,包括了对数据CRUD,而不包含任何业务相关的信息,又是也称为:BaseDAO
·作用:为了实现功能的模块化,更有利于代码的维护和升级。
CTRL + SHIFT + T : 创建测试类
数据库连接池
可以解决传统开发中的数据库连接问题
· 数据库连接池基本思想:就是为数据库建立一个“缓冲池”,预先在缓冲池中放入一 定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕后再放回去。
·数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
·数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由
最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的
最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入等待队列中。
多种开源的数据库连接池
·JDBC数据库连接池使用javax.sql.DataSource来表示,DataSource只是一个接口,该接口通常由服务器(Weblogic,WebSphere,Tomcat)提供实现,也有一些开源的组织提供:
·DBCP由Apache提供。tomcat服务器自带dbcp数据库连接池。速度相对c3p0较快,但自身存在bug(Hibernate3已不再提供支持)
·C3P0是一个开源组织提供的数据库连接池,速度相对较慢,稳定性尚可,Hibernate官方推荐使用
·Druid是阿里提供的数据库连接池
druid配置文件:
Druid.properties
url=jdbc:mysql://localhost:3306/test
username=root
password=ljl19991231
driverClassName=com.mysql.cj.jdbc.Driver
initialSize=10
maxActive=10
通过Druid数据库连接池获取数据库连接
@Test
public void testGetConnectiong() throws Exception {
Properties properties = new Properties();
properties.load(ClassLoader.getSystemClassLoader().getResourceAsStream("Druid.properties"));
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
实际运用:
private static DataSource source = null ;
static {
try {
Properties properties = new Properties();
properties.load(ClassLoader.getSystemClassLoader().getResourceAsStream("Druid.properties"));
source = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection1() throws SQLException {
return source.getConnection();
}
Apache-DBUtils实现CRUD操作
commons-dbutils是Apache组织提供的一个开源的JDBC工具类库,它对JDBC简单的封装,简化了jdbc编码的工作量
API介绍
·org.apache.commons.dbutils.QueryRunner
·org.apache.commons.dbutils.ResultSetHandler
·工具类:org.apache.commons.dbutils.DbUtils
·增删改操作:
public void testInsert(){
try {
//获取API对象
QueryRunner runner = new QueryRunner();
//通过Druid连接池获取连接
Connection con = JDBCUtils.getConnection1();
String sql = "insert into customers(name,email,birth) values(?,?,?)";
runner.update(con,sql,"cxk","cxk@qq.com","2000-12-12");
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
·查询操作:
其中包含参数 ResultSetHandler<T> ,结果集处理器;
Interface ResultSetHandler<T>
该接口中提供了许多实现类:ArrayHandler,ArrayListHandler,BeanHandler,BeanListHandler,ColumnListHandler,
ScalarHandler(特殊值查询,聚合函数)
BeanHandler返回一个对象
//测试查询
@Test
public void testQuery1(){
Connection con = null;
try {
QueryRunner runner = new QueryRunner();
con = JDBCUtils.getConnection1();
String sql = "select id,name,email,birth from customers where id = ?";
BeanHandler<Customer> handler = new BeanHandler<Customer>(Customer.class);
/**
* BeanHandler 是ResultSetHandler接口的实现类,用于封装表中的一条记录
*/
Customer customer = runner.query(con, sql, handler, 23);
System.out.println(customer);
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(con);
}
}
BeanListHandler通过List返回多个对象
@Test
public void testQuery2(){
Connection con = null;
try {
QueryRunner runner = new QueryRunner();
con = JDBCUtils.getConnection1();
System.out.println(con);
String sql = "select id,name,email,birth from customers where id < ?";
BeanListHandler<Customer> listHandler = new BeanListHandler<>(Customer.class);
List<Customer> list = runner.query(con, sql, listHandler, 15);
Iterator<Customer> iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(con);
}
}
MapHandler将一个对象中的属性以键值对形式返回:
@Test
public void testQuery3(){
Connection con = null;
try {
QueryRunner runner = new QueryRunner();
con = JDBCUtils.getConnection1();
System.out.println(con);
String sql = "select id,name,email,birth from customers where id = ?";
MapHandler mapHandler = new MapHandler();
Map<String, Object> map = runner.query(con, sql, mapHandler, 16);
System.out.println(map);
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(con);
}
}
MapListHandler将多个对象的属性以键值对形式返回:
@Test
public void testQuery4(){
Connection con = null;
try {
QueryRunner runner = new QueryRunner();
con = JDBCUtils.getConnection1();
System.out.println(con);
String sql = "select id,name,email,birth from customers where id < ?";
MapListHandler mapListHandler = new MapListHandler();
List<Map<String , Object>> list = runner.query(con, sql, mapListHandler, 16);
System.out.println(list);
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(con);
}
}
ScalarHandler 返回特殊查询的结果:
@Test
public void testQuery5(){
Connection con = null;
try {
QueryRunner runner = new QueryRunner();
con = JDBCUtils.getConnection1();
System.out.println(con);
String sql = "select count(*) from customers where id < ?";
ScalarHandler handler = new ScalarHandler();
Long count =(Long) runner.query(con, sql, handler, 16);
System.out.println(count);
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(con);
}
}
}
自定义ResultSetHandler接口
@Test
public void testQuery7(){
Connection connection1 = null;
try {
QueryRunner runner = new QueryRunner();
connection1 = JDBCUtils.getConnection1();
String sql = "select id, name,email,birth from customers where id = ?";
ResultSetHandler<Customer> handler = new ResultSetHandler<Customer>() {
@Override
public Customer handle(ResultSet rs) throws SQLException {
try {
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
if (rs.next()){
Customer customer = new Customer();
for (int i = 1 ; i <= columnCount ; i++ ){
Object value = rs.getObject(i);
String columnLabel = metaData.getColumnLabel(i);
Field field = customer.getClass().getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(customer,value);
}
return customer;
}
} catch (Exception e) {
e.printStackTrace();
}
return null ;
}
};
Customer query = runner.query(connection1, sql, handler, 23);
System.out.println(query);
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection1);
}
}
}
关闭资源操作
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
</dependency>
上述依赖提供了关闭资源的方法
DbUtils.closeQuietly(con);
DbUtils.closeQuietly(ps);
DbUtils.closeQuietly(rs);