目录
1.4 基于PreparedStatement实现CRUD(创建,读取,更新,删除四种操作)
一、基础篇
1.1 概念
-
JDBC: Java Database Connectivity,意为Java数据库连接,用来操作数据库的。
-
JDBC是Java提供的一组独立于任何数据库管理系统的API。
-
Java提供接口规范,由各个数据库厂商提供接口的实现,厂商提供的实现类封装成jar文件,也就是我们俗称的数据库驱动jar包。
-
学习JDBC,充分体现了面向接口编程的好处,程序员只关心标准和规范,而无需关注实现过程。
1.2 快速入门
【前期准备】
-
准备数据库。
-
官网下载数据库连接驱动jar包。MySQL :: Download MySQL Connector/J (Archived Versions)
-
创建Java项目,在项目下创建lib文件夹,将下载的驱动jar包复制到文件夹里。
-
选中lib文件夹右键->Add as Library,与项目集成。
-
编写代码
【代码如下】主要有6步。
public class Main {
public static void main(String[] args) throws Exception {
// 1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2.获取连接对象
String url="jdbc:mysql://localhost:3306/bd01";
String username="root";
String password="root";
Connection connection = DriverManager.getConnection(url, username, password);
// 3.获取执行SQl语句的对象
Statement statement = connection.createStatement();
// 4.编写SQL语句并执行,接收返回的结果集
String sql="select * from emp";
ResultSet resultSet = statement.executeQuery(sql);
// 5.处理结果集
while(resultSet.next()){//next()方法判断查询的下一行是否为空
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
int age = resultSet.getInt("age");
String job = resultSet.getString("job");
System.out.println(id+"\t"+name+"\t"+age+"\t"+job+"\t\n");
}
// 6.关闭资源
resultSet.close();
statement.close();
connection.close();
}
}
【注意】statement有sql注入的风险所以要用prepareStatement,代码如下:
public class Main {
public static void main(String[] args) throws Exception {
// 1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2.获取连接对象
String url="jdbc:mysql://localhost:3306/bd01";
String username="root";
String password="root";
Connection connection = DriverManager.getConnection(url, username, password);
// 3.预编译SQL语句得到preparedStatement对象
System.out.println("输入你要查询的id:");
Scanner sc = new Scanner(System.in);
int id1=sc.nextInt();
PreparedStatement preparedStatement = connection.prepareStatement("select * from emp where id=?");
// 4. 为?赋值,执行sql,接收返回的结果集。?为占位符防止sql注入,提升效率。
preparedStatement.setInt(1,id1);
ResultSet resultSet = preparedStatement.executeQuery();
// 5.处理结果集
while(resultSet.next()){//next()方法判断查询的下一行是否为空
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
int age = resultSet.getInt("age");
String job = resultSet.getString("job");
System.out.println(id+"\t"+name+"\t"+age+"\t"+job+"\t");
}
// 6.关闭资源
resultSet.close();
preparedStatement.close();
connection.close();
}
}
1.3 核心API讲解
注册驱动
-
注册驱动的意思就是将一个特定的JDBC驱动程序加载到Java虚拟机(JVM)中,使其能够与指定的数据库进行通信。
-
注册驱动的过程,实际上就是通过调用Class.forName()方法,加载并初始化驱动程序中的Driver类。这个Driver类通常会包含一个static块,该static块会向DriverManager注册自身。一旦驱动程序被注册,Java程序就可以通过DriverManager获取与数据库的连接,从而执行SQL语句和获取结果。
-
简单来说,注册驱动就是让JVM知道我们要使用哪个JDBC驱动程序来与数据库进行通信。
1.Connection
-
通过DriverManager对象调用getConnection()获取的
-
Connection接口是JDBCAPI的重要接口,用于建立与数据库的通信通道。换而言之,Connection对象不为空,则代表一次数据库连接。
-
在建立连接时,需要指定数据库URL、用户名、密码参数。
URL: jdbc:mysq!://localhost:3306/使用的数据库名 jdbc:mysql:/IP地址:端口号/数据库名称?参数键值对1&参数键值对2
-
Connection接口还负责管理事务,Connection接口提供了commit和rollback方法,用于提交事务和回滚事务。
-
可以创建Statement对象,用于执行sQL语句并与数据库进行交互。
-
在使用JDBC技术时,必须要先获取Connection对象,在使用完毕后,要释放资源,避免资源占用浪费及泄漏
2.Statement
-
Statement接口用于执行SQL语句并与数据库进行交互。它是JDBC API中的一个重要接口。通过Statement对象,可以向数据库发送SQL语句并获取执行结果。
-
结果可以是一个或多个结果。 增删改︰受影响行数单个结果。 查询:单行单列、多行多列、单行移列等结果。
-
但是Statement接口在执行SQL语句时,会产生SQL注入攻击问题: 当使用statement 执行动态构建的SQL查询时,往往需要将查询条件与SQL语句拼接在一起,直接将参数和SQL语句一并生成,让SQL的查询条件始终为true得到结果。
3.PreparedStatement
PreparedStatement是 Statement接口的子接口,用于执行预编译的SQL查询,作用如下:
-
预编译SQL语句:在创建PreparedStatement时,就会预编译SQL语句,也就是SQL语句已经固定。
-
防止SQL注入: PreparedStatement支持参数化查询,将数据作为参数传递到SQL语句中,采用?占位符的方式,将传入的参数用一对单引号包裹起来",无论传递什么都作为值。有效防止传入关键字或值导致SQL注入问题。
-
性能提升:PreparedStatement是预编译SQL语句,同一SQL语句多次执行的情况下,可以复用,不必每次重新编译和解析。
4.ResultSet
-
Resultset是JDBCAPI中的一个接口,用于表示从数据库中执行查询语句所返回的结果集。它提供了一种用于遍历和访问查询结果的方式。
-
遍历结果: ResultSet可以使用next()方法将游标移动到结果集的下一行,逐行遍历数据库查询的结果,返回值为boolean类型,true代表有下一行结果,false则代表没有。
-
获取单列结果:可以通过getXxx的方法获取单列的数据,该方法为重载方法,支持索引和列名进行获取。
1.4 基于PreparedStatement实现CRUD(创建,读取,更新,删除四种操作)
1.4.1 查询
-
以多行多列为例,代码如下
/ 1. 注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); // 2. 通过DriverManager对象获取连接对象 Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/bd01","root","root"); // 3.预编译SQL语句,获取PreparedStatement对象 PreparedStatement preparedStatement = connection.prepareStatement("select * from emp where id < ?"); // 4.为?赋值,执行SQL语句,返回得到的结果集 preparedStatement.setInt(1,5); ResultSet resultSet = preparedStatement.executeQuery(); // 5.处理结果集 while (resultSet.next()){ int count = resultSet.getInt(1); System.out.println(count); } // 6.释放资源 resultSet.close(); preparedStatement.close(); connection.close(); } }
1.4.1 增,删,改
用法都差不多,都是调用executeUpdate()方法,返回受影响的行数。
-
以新增部门操作为例,代码如下
public class Test02 { public static void main(String[] args) throws Exception { // 1.注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); // 2.获取连接 Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/bd01","root","root"); // 3.预编译SQL语句,获取preparedStatement对象 PreparedStatement preparedStatement = connection.prepareStatement("insert into dept(id,name) value(?,?) "); // 4.为?赋值,执行SQL语句,得到受影响的行数 preparedStatement.setInt(1,0);//id是自增的,不需要赋值也可以 preparedStatement.setString(2,"吹牛部"); int result = preparedStatement.executeUpdate(); // 5.根据受影响的行数,判断是否执行成功 if(result > 0){ System.out.println("成功!"); }else { System.out.println("失败!"); } // 6.释放资源 preparedStatement.close(); connection.close(); } }
二、进阶篇
2.1 jdbc扩展
实体类和ORM思想
-
在使用JDBC操作数据库时,我们会发现数据都是零散的,明明在数据库中是一行完整的数据,到了Java中变成了一个一个的变量,不利于维护和管理。而我们Java是面向对象的,一个表对应的是一个类,一行数据就对应的是Java中的一个对象,一个列对应的是对象的属性,所以我们要把数据存储在一个载体里,这个载体就是实体类。
-
ORM (Object Relational Mapping)思想,对象到关系数据库的映射,作用是在编程中,把面向对象的概念跟数据库中表的概念对应起来,以面向对象的角度操作数据库中的数据,即一张表对应一个类,一行数据对应一个对象,一个列对应一个属性!
-
当下JDBC中这种过程我们称其为手动ORM。后续我们也会学习ORM框架,比如MyBatis、JPA等。
以查询多个部门为例,实现ORM,代码如下:
1.首先定义实体类
public class Department {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Department{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
2.代码编写
public class Test02 {
public static void main(String[] args) throws Exception {
// 1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2.获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/bd01","root","root");
// 3.预编译SQL语句,获取preparedStatement对象
PreparedStatement preparedStatement = connection.prepareStatement("select * from dept where id < ?");
// 4.为?复制,执行SQL语句,得到结果集
preparedStatement.setInt(1,4);
ResultSet resultSet = preparedStatement.executeQuery();
// 5.处理结果集,这里用实体类映射不同的字段,用集合去存放多个实体类。
Department department = null;
ArrayList<Department> departments = new ArrayList<>();
while (resultSet.next()){
department = new Department();
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
department.setId(id);
department.setName(name);
departments.add(department);
}
System.out.println(departments);
// 6.释放资源
resultSet.close();
preparedStatement.close();
connection.close();
}
}
2.2 主键回显
概念
-
在数据中,执行新增操作时,主键列为自动增长,可以在表中直观的看到,但是在Java程序中,我们执行完新增后,只能得到受影响行数,无法得知当前新增数据的主键值。在Java程序中获取数据库中插入新数据后的主键值,并赋值给Java对象,此操作为主键回显。
-
代码演示如下:
public class Test03 {
public static void main(String[] args) throws Exception {
// 1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2.获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/bd01", "root", "root");
// 3.1预编译SQL语句得到PreparedStatement对象
// 3.2后面那个参数表示回显执行成功的主键值。
PreparedStatement preparedStatement =
connection.prepareStatement("insert into dept(name) value (?)", Statement.RETURN_GENERATED_KEYS);
// 4.给?赋值,执行sql语句,得到受影响的行数
Department department = new Department(0,"春牛逼");
preparedStatement.setString(1,department.getName());
int result = preparedStatement.executeUpdate();
// 5.处理结果
ResultSet generatedKeys=null;
if(result > 0){
System.out.println("成功!");
// 得到插入成功后的主键值。(单行单列的结果)
generatedKeys = preparedStatement.getGeneratedKeys();
if(generatedKeys.next()){
// 得到主键值
int id = generatedKeys.getInt(1);
System.out.println(id);
// 赋值给插入成功的那条数据
department.setId(id);
}
}else {
System.out.println("失败!");
}
// 6.1 释放资源
// 6.2 如果没有成功执行generatedKeys有为空,这里要判断一下。
if(generatedKeys!=null){
generatedKeys.close();
}
preparedStatement.close();
connection.close();
//
}
}
2.3 连接池(Druid为例)
存在的问题
-
每次操作数据库都要获取新连接,使用完毕后就close释放,频繁的创建和销毁造成资源浪费。
-
连接的数量无法把控,对服务器来说压力巨大。
概念
-
连接池就是数据库连接对象的缓冲区,通过配置,由连接池负责创建连接、管理连接、释放连接等操作。预先创建数据库连接放入连接池,用户在请求时,通过池直接获取连接,使用完毕后,将连接放回池中,避免了频繁的创建和销毁,同时解决了创建的效率。 当池中无连接可用,且未达到上限时,连接池会新建连接。池中连接达到上限,用户请求会等待,可以设置超时时间。
常见的连接池
-
JDBC的数据库连接池使用javax.sql.DataSource接口进行规范,所有的第三方连接池都实现此接口,自行添加具体实现!也就是说,所有连接池获取连接的和回收连接方法都一样,不同的只有性能和扩展功能!
-
DBCP是Apache提供的数据库连接池,速度相对c3Po较快,但自身存在一些BUG。
-
C3Po是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以。
-
Proxool是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点.
-
Druid是阿里提供的数据库连接池,是集DBCP、C3P0、Proxool优点于一身的数据库连接池,性能、扩展性、易用性都更好,功能丰富。
-
Hikari (U小b)[shi ga li])取自日语,是光的意思,是SpringBoot2.x之后内置的一款连接池,基于BoneCP (已经放弃维护,推荐该连接池)做了不少的改进和优化,口号是快速、简单、可靠。
使用
1.准备,导入需要的jar包
2.硬编码方式实现
public class Test05 {
public static void main(String[] args) throws SQLException {
/*
连接池的使用,硬编码方式
分为4步
1.创建DruidDataSource连接对象
2.设置连接池的配置信息,必须和非必须
3.通过连接池获取连接对象
4.回收连接(将连接归还给连接池,给其他线程复用)
* */
// 1.创建连接池对象
DruidDataSource druidDataSource = new DruidDataSource();
// 2.设置配置信息,必须
druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
druidDataSource.setUrl("jdbc:mysql:///bd01");
druidDataSource.setUsername("root");
druidDataSource.setPassword("root");
// 非必须
druidDataSource.setInitialSize(10);//设置连接数量
druidDataSource.setMaxActive(20);//最大连接数,当超过10个时,再创建,最大20个
// 3.通过连接池获取连接对象
DruidPooledConnection connection = druidDataSource.getConnection();
// 获取连接之后,其他操作一样。。。。。。。。
System.out.println(connection);
// 4.回收连接
connection.close();
}
}
3.软编码方式实现(推荐)
首先建一个目录resources,将其标记为资源目录,然后创建文件pro.properties,配置内容如下:
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///bd01
username=root
password=root
其次就是读取配置文件信息,建立连接的过程,代码如下:
public class Test06 {
public static void main(String[] args) throws Exception {
// 1.创建properties集合,用于存储外部配置文件的key和value值
Properties properties = new Properties();
// 2.读取外部配置文件,获取输入流,加载到properties集合中去
// 只要加载Test06这个类的时候就得到读取pro.properties文件的流
InputStream inputStream = Test06.class.getClassLoader().getResourceAsStream("pro.properties");
// 将读取的信息加载到集合中
properties.load(inputStream);
// 3.基于properties集合构建DruidDataSource连接池
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
// 4.获取连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
// 进行连接后的操作
// 5.回收连接
connection.close();
}
}
2.4 properties集合
概念
-
Properties 集合是 Java 提供的一个类,用于存储可以通过 String 键来访问的 String 值。它继承自 Hashtable<Object,Object> 类,但它有一个特性,即它的键和值都是字符串。由于这个特性,Properties 类经常用于处理配置文件,如 .properties 文件。
主要特点
-
键和值都是字符串:Properties 集合只能存储字符串类型的键和值。
-
可持久化:Properties 集合提供了将键值对保存到文件(如 .properties 文件)或从文件中加载键值对的方法。
-
继承自 Hashtable:虽然 Properties 类扩展了 Hashtable,但不建议使用 Hashtable 的特定方法(如 put() 和 get()),因为 Properties 类已经提供了自己的 setProperty() 和 getProperty() 方法。
核心方法介绍
-
getProperty(String key)
根据给定键去查找对应的值,找到了返回该值,否则返回空。
-
setProperty(String key, String value)
将一个键值对添加到集合中,如果已经存在该键就替换掉其值。
-
load(InputStrean inStream)
这个方法可以从输入流中加载属性列表。比如,我们有一个.properties文件,里面存储了一些键值对,我们就可以使用这个方法把这个文件的内容加载到Properties集合中。
-
store(OutputStream out, String comments)
这个方法可以把Properties集合中的键值对存储到输出流中。通常,我们会把它用来把Properties集合的内容保存到.properties文件中。comments参数是一个注释,它会被存储到文件的开头。
-
stringPropertyNames()
这个方法返回一个Set集合,其中包含Properties集合中所有的键。这些键及其对应的值都是字符串类型。
三、高级篇
3.1 jdbc工具类封装
JDBCUtil(v1.0版本)
-
维护一个连接池对象
-
对外提供在连接池中获取连接的方法
-
对外提供回收连接的方法
-
工具类对外仅仅提供共性的功能代码,方法均为静态方法!
properties配置文件
driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql:///bd01 username=root password=root
【注】也可以用资源目录对象的方法读取文件信息
static {
//通过资源目录读取配置文件
ResourceBundle jdbc = Resources.getBundle("jdbc");
String driverClassName = jdbc.getString("driverClassName");
String url = jdbc.getString("url");
String username = jdbc.getString("username");
String password = jdbc.getString("password");
try {
Class.forName(driverClassName);
connection = DriverManager.getConnection(url, username, password);
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
工具类代码
public class JDBCUtil1 {
// 创建连接池引用,因为要提供给当前项目的全局使用,创建为静态
private static DataSource dataSource;
// 在项目启动时,即创建连接池对象,赋值给dataSource;
static {
try {
Properties properties = new Properties();
InputStream inputStream = JDBCUtil1.class.getClassLoader().getResourceAsStream("pro.properties");
properties.load(inputStream);
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// 得到连接对象
public static Connection getConnection(){
try {
return dataSource.getConnection();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
// 回收连接
public static void release(Connection connection){
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
测试代码
public class Test07 { public static void main(String[] args) { Connection connection = JDBCUtil1.getConnection(); System.out.println(connection); JDBCUtil1.release(connection); } }
JDBCUtil(v2.0版本)
ThreadLocal介绍
-
ThreadLocal,给每个线程提供了一个独立的存储空间,这样每个线程都可以读取、修改自己存储空间内的值,而不会影响其他线程。它就像一个为每个线程定制的“小盒子”,每个线程都有自己的“小盒子”,互不干扰。
-
主要有下面几个方法:
set(T value):往当前线程的“小盒子”里放个值。 get():从当前线程的“小盒子”里取值。 initialValue():获取当前线程的“小盒子”里的初始值,这个方法在第一次调用get()方法时才会被调用。 remove():删除当前线程的“小盒子”里的值,这个操作通常是为了减少内存占用。 ThreadLocal的优点很明显,就是线程安全、高效、简单易用。但也要注意,如果使用不当,也可能会导致内存泄漏等问题。
编写工具类
-
维护一个连接池对象、维护了一个线程绑定变量的ThreadLocal对象
-
对外提供在ThreadLocal中获取连接的方法
-
对外提供回收连接的方法,回收过程中,将要回收的连接从ThreadLocal中移除!
-
注意:使用ThreadLocal就是为了一个线程在多次数据库操作过程中,使用的是同一个连接!
工具类代码
public class JDBCUtil2 {
//创建连接池引用,因为要提供给当前项目的全局使用,创建为静态
private static DataSource dataSource;
//用ThreadLocal去存放连接,用的时候直接在这里面取,保证拿到的是一个连接,避免了一个用户从连接池中拿多个连接,防止资源的浪费。
private static ThreadLocal<Connection> threadLocal=new ThreadLocal<>();
static {
try {
Properties properties = new Properties();
InputStream inputStream = JDBCUtil1.class.getClassLoader().getResourceAsStream("pro.properties");
properties.load(inputStream);
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// 得到连接对象
public static Connection getConnection(){
try {
// 从threadLocal中获取连接
Connection connection = threadLocal.get();
// 如果连接等于空,说明是第一次连接
if (connection == null) {
// 从连接池中拿出一个连接放到threadLocal中
connection= dataSource.getConnection();
threadLocal.set(connection);
}
return connection;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// 回收连接
public static void release(){
try {
Connection connection = threadLocal.get();
// 1.如果不等于null说明已经threadLocal中存储了一个连接,将其清除
// 2.然后再将从中拿出的连接关闭
if (connection!=null){
threadLocal.remove();
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
3.2 Dao层和BaseDao工具类
Dao概念
-
DAO: Data Access Object,数据访问对象。
-
Java是面向对象语言,数据在Java中通常以对象的形式存在。一张表对应一个实体类,一张表的操作对应一个DAo对象!
-
在Java操作数据库时,我们会将对同一张表的增删改查操作统一维护起来,维护的这个类就是DAO层。
-
DAO层只关注对数据库的操作,供业务层service调用,将职责划分清楚!
BaseDao概念
-
基本上每一个数据表都应该有一个对应的DAo接口及其实现类,发现对所有表的操作(增、删、改、查)代码重复度很高,所以可以抽取公共代码,给这些DAo的实现类可以抽取一个公共的父类,复用增删改查的基本操作,我们称为BaseDAO。
jdbc操作数据库有6个步骤:
-
注册驱动
-
获取连接
-
预编译SQL语句,得到PreparedStatement对象
-
为?赋值,执行SQL语句,返回结果集
-
处理结果集
-
释放资源
1,2,6三个步骤已经被封装到JDBCUtil工具类中,我们需要把3,4,6的共性代码,也封装到一个BaseDao工具类中,从而简化代码的书写。对于BaseDao工具类
-
返回值可能结果:整数,单个对象,或者是一个对象的集合。
-
参数:一个sql语句,一个可变参数列表,还有一个类对象(不确定是什么对象,需要用反射)
编写BaseDAO工具类
public class BaseDAO {
/*
* 一、通用的增,删,改的方法
* 返回值:受影响的行数
* 参数:
* 1.String类型的sql语句
* 2.为占位符赋值的object类型的可变参数
* */
public static int executeUpdate(String sql,Object...params) throws Exception {
// 1.通过JDBCUtil2获取连接
Connection connection = JDBCUtil2.getConnection();
// 2.预编译SQL语句,获取PreparedStatement对象
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 3.为占位符?赋值,执行sql语句
if(params!=null && params.length > 0){
for (int i = 0; i < params.length; i++) {
preparedStatement.setObject(i+1,params[i]);
}
}
int row = preparedStatement.executeUpdate();
// 释放资源
preparedStatement.close();
if(connection.getAutoCommit()){
JDBCUtil2.release();
}
// 返回受影响的行数
return row;
}
/*
* 二、通用的查询方法:多行多列,单行多列,单行单列
* 返回值:多行多列:List<Department>
单行多列:Department
* 单行单列:一个结果:Double,Integer
* 封装过程:
* 1.返回类型:不确定,用泛型。
* 2.返回结果:通用,用List集合,存储多个结果,单个结果为get(0)
* 3.结果的封装:反射,要求调用者告知BaseDAO要封装对象的类对象,Class
* */
public static <T> List<T> executeQuery(Class<T> tClass,String sql,Object...params) throws Exception {
// 1.用工具类建立连接
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++) {
preparedStatement.setObject(i+1,params[i]);
}
}
// 4.获取结果集
ResultSet resultSet = preparedStatement.executeQuery();
// 5.处理结果集
// 5.1 获取结果集中的元数据对象(将查询的结果当成一个对象)
// 5.2 这个对象中包含获取列的数量和列名等方法。
ResultSetMetaData metaData = resultSet.getMetaData();
List<T> list = new ArrayList<>();
while (resultSet.next()){
// 循环一次,有一行数据,创建一个对象去接收,因为不知道是什么对象,所以用反射去创建未知的对象
T t=tClass.newInstance();
// 得到当前行的每一列,metaData.getColumnCount()获取列数
for (int i = 0; i < metaData.getColumnCount(); i++) {
// 获取每列的值
Object value = resultSet.getObject(i + 1);
// 得到列名,也就是对象的属性名
String columnName = metaData.getColumnName(i + 1);
// 用反射去获取对象的属性,然后为其赋值
Field field = tClass.getDeclaredField(columnName);
// 暴力破解
field.setAccessible(true);
// 为该对象属性赋值
field.set(t,value);
}
// 将查询的结果封装到对象里后,在存储到集合中去。
list.add(t);
}
resultSet.close();
preparedStatement.close();
// 如果开启了自动提交就释放
if(connection.getAutoCommit()){
JDBCUtil2.release();
}
return list;
}
/*
* 获取单行单列的结果
* */
public static <T> T executeQueryBean(Class<T> tClass,String sql,Object...params){
List<T> query = null;
try {
query = executeQuery(tClass, sql, params);
} catch (Exception e) {
throw new RuntimeException(e);
}
return query.get(0);
}
}
编写Dao层
1.编写接口
public interface Depar {
// 查询所有
List<Department> selectAll();
// 根据id查询
Department selectById(Integer id);
// 新增部门
int insertDepart(Department d);
// 修改部门
int update(Department d);
// 删除部门
int delete(int d);
}
2.编写实现类(用工具类,简化了很多重复代码的编写)
public class DepartmentImp implements Depar {
@Override
public List<Department> selectAll() {
List<Department> departments=null;
try {
departments = BaseDAO.executeQuery(Department.class, "select * from dept");
} catch (Exception e) {
throw new RuntimeException(e);
}
return departments;
}
@Override
public Department selectById(Integer id) {
Department department =
BaseDAO.executeQueryBean(Department.class,"select * from dept where id=?",id);
return department;
}
@Override
public int insertDepart(Department d) {
int row=0;
try {
row= BaseDAO.executeUpdate("insert into dept(name) value (?)",d.getName() );
if(row > 0){
System.out.println("新增成功!");
}else {
System.out.println("新增失败!");
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return row;
}
@Override
public int update(Department d) {
try {
int row = BaseDAO.executeUpdate("update dept set name=? where id=?", d.getName(),d.getId());
if(row > 0){
System.out.println("修改成功!");
}else {
System.out.println("修改失败!");
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return 0;
}
@Override
public int delete(int d) {
int row=0;
try {
row=BaseDAO.executeUpdate("delete from dept where id=?",d);
if(row > 0){
System.out.println("删除成功!");
}else {
System.out.println("删除失败!");
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return row;
3.3 jdbc中事务实现
以转账为例
public class Test03 {
public static void main(String[] args) {
Connection connection = JDBCUtil2.getConnection();
try {
// 1.将事务设置为手动提交
connection.setAutoCommit(false);
// id为1的加100
AccountImp.addMoney(100,1);
//出现异常
int a=10/0;
// id为2的减100
AccountImp.subMoney(100,2);
// 2.提交事务
connection.commit();
} catch (Exception e) {
try {
// 3.当出现异常时就回滚事务
connection.rollback();
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
throw new RuntimeException(e);
}finally {
JDBCUtil2.release();
}
}
}
标签:JDBC,一篇,学会,连接,connection,id,public,连接池,String
From: https://blog.csdn.net/qq_62555748/article/details/140671190