简介
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封装多个对象:*创建一个数组将封装好的对象放入数组中;* 在循环中创建一个对象,下一次循环会将改对象覆盖掉,所以在依次循环的最后要赋值完毕的对象保存在集合中*/@Testpublic 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()-----------------获得连接对象
代码
@Testpublic 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 开发CRUDSystem.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()方法
@Testpublic 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连接池配置对象,构建HikariDataSourceHikariDataSource 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中获取ConnectionConnection 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();//如果开启了事务的手动提交,提交操作完毕后,还给连接池之前要将事务的手动提交改为trueconnection.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);//突破封装的privatefield.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)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的, 接下来的其他操作和数据库故障不应该对其有任何影响
- 事务的提交方式:
- 自动提交:每条语句自动存储一个事务中,执行成功自动提交,执行失败自动回滚!
- 手动提交: 手动开启事务,添加语句,手动提交或者手动回滚即可!
注意点:当开启事务后,切记一定要根据代码执行结果来决定是否提交或回滚!否则数据库看不到数据的操作结果