简介
JDBC简单执行过程:
总结Java提供接口;数据库厂商提供实现;程序员调用接口;接口调用实现类,连接操作数据库
JDBC的概念
JDBC是Java提供的一组独立于任何数据库管理系统的API。
java操作数据库
步骤:
1 注册驱动(将数据库厂商得驱动类通过类加载得方式加载到程序中)
从JDK6开始,不再需要显式地调用 `Class.forName()` 来加载 JDBC 驱动程序,只要在类路径中集成了对应的jar文件,会自动在初始化时注册驱动程序。
2 通过DriverManager调用getConnection()传入主机地址端口号要使用得数据库,用户名,用户密码---------以此来获得一个连接对象
Connection接口是JDBC API的重要接口,用于建立与数据库的通信通道
在建立连接时,需要指定数据库URL、用户名、密码参数。
3 通过connection对象调用createStatement()方法获得statement对象(是发送SQL语句的对象)
`Statement` 接口用于执行 SQL 语句并与数据库进行交互。可以向数据库发送 SQL 语句并获取执行结果。
PreparedStatement(处理SQL注入攻击问题)
防止SQL注入:`PreparedStatement` 支持参数化查询,将数据作为参数传递到SQL语句中,采用?占位符的方式,将传入的参数用一对单引号包裹起来'',无论传递什么都作为值。有效防止传入关键字或值导致SQL注入问题。
4 编写SQL语句(语法与在Mysql中写的一样)
5 通过statement调用executeQuery()方法,传入SQL语句-----------依次获得resultSet(结果集)
在MySQL中查询的结果被封装在resultSet中,resultSet中有行和列的数据存储
6 通过resultSet调用getXXX()方法获得数据
查询结果中该列的数据是什么类型的就调用 get该类型()----------可通过列名也可通过索引获取函数
7 关闭资源 connection连接对象 statement传输对象 resultSet结果集
实体类搭建与ORM封装对象
实体类和ORM
> - 在使用JDBC操作数据库时,我们会发现数据都是零散的,明明在数据库中是一行完整的数据,到了Java中变成了一个一个的变量,不利于维护和管理。而我们Java是面向对象的,一个表对应的是一个类,一行数据就对应的是Java中的一个对象,一个列对应的是对象的属性,所以我们要把数据存储在一个载体里,这个载体就是实体类!
> - ORM(Object Relational Mapping)思想,**对象到关系数据库的映射**,作用是在编程中,把面向对象的概念跟数据库中表的概念对应起来,以面向对象的角度操作数据库中的数据,即一张表对应一个类,一行数据对应一个对象,一个列对应一个属性!
例如将下面的表封装为一个个对象
/*ORM封装多个对象:
*创建一个数组将封装好的对象放入数组中;
* 在循环中创建一个对象,下一次循环会将改对象覆盖掉,所以在依次循环的最后要赋值完毕的对象保存在集合中
*/
@Test
public void testORMList() throws SQLException {
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "abc123LQ");
PreparedStatement preparedStatement = connection.prepareStatement("SELECT emp_id,emp_name,emp_salary,emp_age FROM t_emp");
ResultSet resultSet = preparedStatement.executeQuery();
Employee employee=null;
//将多个对象保存在集合中;
List<Employee> employeeList=new ArrayList<>();
while(resultSet.next()){
employee=new Employee();//下一次循环会将改对象覆盖掉,所以在依次循环的最后要赋值完毕的对象保存在集合中
int emp_id = resultSet.getInt("emp_id");
String emp_name = resultSet.getString("emp_name");
double emp_salary = resultSet.getDouble("emp_salary");
int emp_age = resultSet.getInt("emp_age");
//为对象属性赋值
employee.setEmpId(emp_id);
employee.setEmpName(emp_name);
employee.setEmpSalary(emp_salary);
employee.setEmpAge(emp_age);
//将每次循环的一行数据对象存储在集合里
employeeList.add(employee);
}
//遍历集合
for (Employee employee1 : employeeList) {
System.out.println(employee1);
}
//资源释放
resultSet.close();
preparedStatement.close();
connection.close();
}
主键回显
- 在数据中,执行新增操作时,主键列为自动增长,可以在表中直观的看到,但是在Java程序中,我们执行完新增后,只能得到受影响行数,无法得知当前新增数据的主键值。在Java程序中获取数据库中插入新数据后的主键值,并赋值给Java对象,此操作为主键回显。
批量操作
插入多条数据时,一条一条发送给数据库执行,效率低下!
通过批量操作,可以提升多次操作效率!
批量操作
Java默认连接Mysql是无法进行皮量操作的
步骤:1 必须在连接数据库的URL后追加?rewriteBatchedStatements=true
将jdbc:mysql:///atguigu改为jdbc:mysql:///atguigu?rewriteBatchedStatements=true
2 新增SQL必须用values,且语句最后不要追加;结束
3 通过prepareStatement调用addBatch()方法-------------将SQL语句进行批量添加操作
4 通过prepareStatement调用executeBatch()方法---------------------统一执行批量操作
连接池
现有问题
> - 每次操作数据库都要获取新连接,使用完毕后就close释放,频繁的创建和销毁造成资源浪费。
> - 连接的数量无法把控,对服务器来说压力巨大。
连接池
> 连接池就是数据库连接对象的缓冲区,通过配置,由连接池负责创建连接、管理连接、释放连接等操作。
> 预先创建数据库连接放入连接池,用户在请求时,通过池直接获取连接,使用完毕后,将连接放回池中,避免了频繁的创建和销毁,同时解决了创建的效率。
> 当池中无连接可用,且未达到上限时,连接池会新建连接。
> 池中连接达到上限,用户请求会等待,可以设置超时时间。
Druid软编码实现
1 创建Properties集合,用于存储外部配置文件的key与value值
创建Properties对象
2 读取外部配置文件,获取输入流,加载到properties集合里
加载类时,创建输入流
类加载;类名.class.getClassLoader()
创建输入流:在类加载后调用getResourceAsStream()方法,出入要读取的文件路径
加载到properties集合里:通过properties调用load()方法
3 基于properties集合构建DruidDataSource连接池
通过DruidDataSourceFactory调用createDataSource()方法,出入已读取好文件的properties对象-----------获得连接池(dataSource)
4 通过连接池获取连接对象
通过dataSource连接池调用getConnection()-----------------获得连接对象
代码
@Test
public void testResourcesDruid() throws Exception {
//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();
//5 开发CRUD
System.out.println(connection);
//6 回收连接
connection.close();
}
HikariCP软编码实现
1 创建Properties集合,用于存储外部配置文件的key与value值--------------通过Properties构造器创建properties对象
2 读取外部配置文件,获取输入流,加载到properties集合里
加载类时,创建输入流
类加载;类名.class.getClassLoader()
创建输入流:在类加载后调用getResourceAsStream()方法,出入要读取的文件路径
加载到properties集合里:通过properties调用load()方法
3 创建HikariConfig连接池配置对象,将properties集合传进去
通过HikariConfig构造器创建连接池配置对象,传入properties
4 基于HikariConfig连接池配置对象,构建HikariDataSource
通过调用HikariDataSource的构造器,传入HikariConfig创建连接池
5 通过连接池获取连接对象
通过HikariDataSource的对象调用getConnection()方法
@Test
public void testResourcesHikari() throws IOException, SQLException {
//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);
Connection connection = hikariDataSource.getConnection();
//5 获取连接
System.out.println(connection);
//6 回收连接
connection.close();
}
工具类:
JDBC优化及工具类封装
现有问题
> 我们在使用JDBC的过程中,发现部分代码存在冗余的问题:
> - 创建连接池。
> - 获取连接。
> - 连接的回收。
JDBC工具类
1 维护连接池对象(该连接池在整个项目中都可以使用);维护了一个线程绑定变量的ThreadLocal对象
在本地线程里存一个连接对象,此值可以在当前项目中任何位置都可以拿到----------在一个项目中使用的是同一个连接对象
2 对外提供在ThreadLocal中获取连接的方法
3 对外提供收回连接的方法,回收过程中,将要回收的连接从ThreadLocal中移除
注意:工具类仅对外提供共性的功能代码,所以方法均为静态方法
使用ThreadLocal就是为了一个线程在对此数据库操作过程中。使用的是同一个连接
代码:
public class JDBCUtil2 {
//创建连接池引用,因为要提供为当前项目的全全局使用,所以创建为静态的
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();
if(connection==null){//threadLocal中没有存储Connection,也就是第一次获取
//在连接池中获取一个连接,存储在threaLocal里
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){
//从threaLocal中移除当前已经存储的connection对象
threadLocal.remove();
//如果开启了事务的手动提交,提交操作完毕后,还给连接池之前要将事务的手动提交改为true
connection.setAutoCommit(true);---------------------------------事务
//将connection对象归还连接池
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}//-------------------------------------------------------------------对外提供收回连接的方法
}
DAO概念及搭建
DAO:Data Access Object,数据访问对象。
> Java是面向对象语言,数据在Java中通常以对象的形式存在。一张表对应一个实体类,一张表的操作对应一个DAO对象!
> 在Java操作数据库时,我们会将对同一张表的增删改查操作统一维护起来,维护的这个类就是DAO层。
> DAO层只关注对数据库的操作,供业务层Service调用,将职责划分清楚!
BaseDAO概念
> 基本上每一个数据表都应该有一个对应的DAO接口及其实现类,发现对所有表的操作(增、删、改、查)代码重复度很高,所以可以抽取公共代码,给这些DAO的实现类可以抽取一个公共的父类,复用增删改查的基本操作,我们称为BaseDAO。
代码及思路:
{
/**
* 通用的增删改方法
* @param sql 调用者要执行的sql语句
* @param params SQL语句中占位符要赋值的参数
* @return 返回受影响的行数
*/
public int executeUpdate(String sql,Object... params) throws Exception {
//1 通过JDBCUtil2获取数据库连接
Connection connection = JDBCUtil2.getConnection();
//2 预编译SQL语句
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//3 为占位符赋值
if(params!=null && params.length>0){//防止没有为占位符输入要赋值的参数
for(int i=0;i<params.length;i++){
//占位符是从以开始的,参数的数组是从0开始的
preparedStatement.setObject(i+1,params[i]);
}
}
int row = preparedStatement.executeUpdate();
//5 释放资源(要在返回值前释放)
preparedStatement.close();
if(connection.getAutoCommit()){
JDBCUtil2.release();
}
//4 返回结果
return row;
}
//BaseDAO搭建通用查询方法
/*
思路:
通用的查询:多行多列,单行多列,单行单列
多行多列 List<T>
单行多列 对象
单行单列 封装的是一个结果 ,Double Integer....
封装过程:
1 返回的类型:泛型:类型不确定,调用者知道;调用时,将此次查询的结果类型告知BaseDAO就可以了
2 返回的结果:通用,List 可以存储多个结果,也可以存储单个结果 get(0)
3 结果的封装:反射,要求调用者告知BaseDAO要封装对象的类对象
*/
public <T> List<T> executeQuery(Class<T> clazz,String sql,Object... params) throws Exception {
Connection connection = JDBCUtil2.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql);
if(params!=null&¶ms.length>0){
for(int i=0;i<params.length;i++){
preparedStatement.setObject(i+1,params[i]);
}
}
ResultSet resultSet = preparedStatement.executeQuery();
//获取结果集中元数据对象------------------------------将查询结果的整张表看成一个对象
//包含了列的数量,每个列的名称
ResultSetMetaData metaData = preparedStatement.getMetaData();
//列数
int columnCount = metaData.getColumnCount();
List<T> list=new ArrayList<>();
//处理结果
while (resultSet.next()){
//循环一次,代表有一行数据,就创建一个对象将其存储起来
//通过反射创建对象
T t=clazz.newInstance();
//通过循环遍历当前行的列,获取每一列的数据
for(int i=1;i<=columnCount;i++){
//通过下标获取列的值,获取到的列的value值,这个值就是t这个对象中的某一属性的值
Object value = resultSet.getObject(i);
//获取当前拿到的列的名字=对象的属性名
String filedName = metaData.getColumnLabel(i);
//通过类的对象和filedName获取要粉装的对象的属性
Field field = clazz.getDeclaredField(filedName);
//突破封装的private
field.setAccessible(true);
field.set(t,value);
}
list.add(t);
}
resultSet.close();
preparedStatement.close();
if(connection.getAutoCommit()){
JDBCUtil2.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);
}
}
事务
- 数据库事务就是一种SQL语句执行的缓存机制,不会单条执行完毕就更新数据库数据,最终根据缓存内的多条语句执行结果统一判定! 一个事务内所有语句都成功及事务成功,我们可以触发commit提交事务来结束事务,更新数据! 一个事务内任意一条语句失败,即为事务失败,我们可以触发rollback回滚结束事务,数据回到事务之前状态!
- 事务的特性:
1. 原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生, 要么都不发生。
2. 一致性(Consistency)事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
3. 隔离性(Isolation)事务的隔离性是指一个事务的执行不能被其他事务干扰, 即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
4. 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的, 接下来的其他操作和数据库故障不应该对其有任何影响
- 事务的提交方式:
- 自动提交:每条语句自动存储一个事务中,执行成功自动提交,执行失败自动回滚!
- 手动提交: 手动开启事务,添加语句,手动提交或者手动回滚即可!
注意点:当开启事务后,切记一定要根据代码执行结果来决定是否提交或回滚!否则数据库看不到数据的操作结果