JDBC 学习笔记
文章目录
一、JDBC (Java Database Connectivity)
1.1 JDBC数据库驱动
mysql-connector-java-8.0.x 适用于8.x版本
1.2 JDBC API
JDBC是由多个接口和类进行功能实现。
类型 | 权限定名 | 简介 |
---|---|---|
class | java.sql.DriverManager | 管理多个数据库驱动类,提供了获取数据库连接的方法 |
interface | java.sql.Connection | 代表一个数据库连接(Connection != null 表示已连接数据库) |
interface | java.sql.Statement | 发送SQL语句到数据库工具 |
interface | java.sql.ResultSet | 保存SQL查询语句的结果数据(结果集) |
class | java.sql.SQLException | 处理数据库应用程序时所发生的异常 |
1.3 核心思想
1.4 环境搭建
- 在项目下新建lib文件夹,用以存放jar文件
- 将mysql驱动
mysql-connector-java-8.0.x
复制到项目lib文件夹中 - 选中lib文件夹右键选择Add as library
二、JDBC开发步骤【重点】
2.1 注册驱动
Class.forName(); 触发类加载
使用Class.forName(“com.mysql.jdbc.Driver”);手动加载字节码文件到 JVM 中
Class.forName("com.mysql.jdbc.Driver");//加载驱动
2.2 连接数据库
- 通过DriverManager.getConnection(url, user, password)获取数据库连接对象
- URL: jdbc:mysql://localhost:3306/database
- username: root
- password: root
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/database?serverTimezone=GMT&useUnicode=true&character=utf8","root","root");
//serverTimezone=GMT 解决时区不一致问题
//useUnicode=true&character=utf8 设置编码格式
2.3 获取发送sql对象
通过上面Connection对象获得Statement对象,用于对数据库进行通用访问
Statement statement = connection.createStatement();
2.4 执行SQL语句
执行SQL语句并接收执行结果
String sql = "INSERT INTO jobs(job_id, job_title, min_salary, max_salary) VALUE('JAVA', 'Java Developer', 4500, 15000);";
int result = statement.executeUpdate(sql);//执行sql语句并接收结果
- 注:在编写DML语句时,一定要注意字符串参数的符号是 ’ value’
- DML语句:增删改,返回受影响行数(int类型)
- DQL语句:查询,返回结果数据(ResultSet结果集)
2.5 处理结果
接受处理操作结果
if(result == 1) {
System.out.println("Success");
}
- 受影响行数:逻辑判断、方法返回
- 查询结果集:迭代、依次获取
2.6释放资源
遵循 先开后关 原则,释放所使用到的对象
statement.close();
connection.close();
三、Result(结果集)
在执行查询sql后,存放查询到的结果集数据
3.1 接收结果集
ResultSet resultSet = statement.excuteQuery("SELECT * FROM employees;");
3.2 遍历ResultSet中的数据
ResultSet以表(table)结构进行临时结果的存储,需要通过 JDBC API 将其中数据进行依次捕获
- 数据行指针:初始位置在第一行数据前,每调用一次boolean next()方法ResultSet的指针向下移动一行,结果为true,表示当前行有数据。
- resultSet.getXxx(整数);代表根据列的编号顺序获得,从1开始。
- resultSet.getXxx(“列名”);代表根据列名获得
boolean next() throws SQLException //判断resultSet结果集中下一行是否有数据
3.2.1 遍历方法
int getInt(int columnIndex) throws SQLException //获取当前行第N列的int值
int getInt(String columnLabel) throws SQLException //获取当前行columnLabel的int值
int getDouble(int columnIndex) throws SQLException //获取当前行第N列的double值
int getDouble(String columnLabel) throws SQLException //获取当前行columnLabel的double值
int getString(int columnIndex) throws SQLException //获取当前行第N列的string值
int getString(String columnLabel) throws SQLException //获取当前行columnLabel的string值
...
四、常见错误
- java.lang.ClassNotFountException:找不到类(类名书写错误、没有导入jar包)
- java.sql.SQLException:与sql语句相关的错误(约束错误、表名列名书写错误)建议:在客户端工具中测试SQL语句之后再粘贴在代码中
- com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 列值String类型没有加单引号
- Duplicate entry ‘1’ for key ‘PRIMARY’:主键已存在或存在混乱,更改主键值或清空表
- com.mysql.jdbc.exception,jdbc4.MySQLSyntaxException: Unknown column ‘password’ in:可能输入的值类型不对,确定是否插入的元素时对应的值的类型正确
五、PreparedStatement【重点】
PreparedStatement 继承了 Statment 接口,执行SQL语句的方法无异
5.1 PreparedStatement的应用
作用:1、预编译SQL语句,效率高
2、安全,避免SQL注入
3、可以动态的填充数据,执行多个同构的SQL语句(保证同一个sql语句)
5.1.1 参数标记
//1、预编译 SQL 语句
PreparedStatement preparedStatement = connection.preparedStatement("SELECT * FROM user where username = ? and password = ?");
5.1.2 动态参数绑定
prepared.setXxx(下标,值) 参数下标从1开始,为指定参数下标绑定值
//1、预编译 SQL 语句
PreparedStatement preparedStatement = connection.preparedStatement("SELECT * FROM user where username = ? and password = ?");
//2、为参数下标赋值
preparedStatement.setString(1, username);
preparedStatement.setString(2, password);
六、封装工具类
- 在实际JDBC的使用中,存在着大量的重复性代码:例如连接数据库、关闭数据库等这些操作
- 我们需要把传统的JDBC代码进行重构,抽取出通用的JDBC类,以后连接任何数据库、释放资源都可以使用这个工具类
6.1 重用性方案
- 封装获取连接、释放资源两个方法:
- 提供public static Connection getConnection(){}方法
- 提供public static void closeAll(Connection con, Statement sm, ResultSet rs){}方法
6.1.1 重用工具类实现
public class DBUtils {
/**
* 只加载一次到JVM
*/
static {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException exception) {
exception.printStackTrace();
}
}
/**
* 获取连接
* @return 返回连接对象
* @throws ClassNotFoundException 无法找到类
*/
public static Connection getConnection() throws ClassNotFoundException {
Connection connection = null;
try {
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/companydb?serverTimezone=GMT&useUnicode=true&character=utf8","root", "root");
} catch (SQLException exception) {
exception.printStackTrace();
}
return connection;
}
/**
* 关闭资源
* @param connection
* @param statement
* @param resultSet
*/
public static void closeAll(Connection connection, Statement statement, ResultSet resultSet) {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException exception) {
exception.printStackTrace();
}
}
}
6.2 跨平台方案
- 定义public static final Properties prop = new Properties(); //读取配置文件的Map
- 定义static{
- //首次使用工具类时,加载驱动
- InputStream is = JDBCUtil.class.getResourceAsStream(“路径”); //通过服用本类自带流,读取jdbc.properties配置文件,classPath = bin
- prop.load(is); //通过prop对象将流中的配置信息分割成键值对
- String driverName = prop.getProperty(“driver”); //通过driverName的键获取对应的值(com.mysql.jdbc.Driver)
- Class.forName(driverName); //加载驱动
- }
6.2.1 跨平台工具类实现
创建配置文件 db.properties
driver = com.mysql.jdbc.Driver
url = jdbc:mysql://localhost:3306/databaseName
username = root
password = root
工具类封装
public class DBUtilprop {
private static final Properties PROPERTIES = new Properties();//存储配置文件的Map集合
static {
InputStream is = DBUtilprop.class.getResourceAsStream("/db.properties");
try {
PROPERTIES.load(is);
Class.forName(PROPERTIES.getProperty("driver"));
} catch (IOException exception) {
exception.printStackTrace();
} catch (ClassNotFoundException exception) {
exception.printStackTrace();
}
}
//获取连接
public static Connection getConnection() throws ClassNotFoundException {
Connection connection = null;
try {
connection = DriverManager.getConnection(PROPERTIES.getProperty("url"),
PROPERTIES.getProperty("username"), PROPERTIES.getProperty("password"));
} catch (SQLException exception) {
exception.printStackTrace();
}
return connection;
}
//关闭资源
public static void closeAll(Connection connection, Statement statement, ResultSet resultSet) {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException exception) {
exception.printStackTrace();
}
}
}
七、ORM
ORM(Object Relational Mapping)
从数据库查询到的结果集(ResultSet)正进行遍历时,逐行遍历取出的都是零散数据。在实际应用开发中,我们需要将零散的数据进行封装整理
7.1 实体类(Entity):零散数据的载体
- 一行数据中,多个零散的数据进行整理
- 通过entity的规则对表中的数据进行对象的封装
- 表名=类名;列名=属性名;提供各个属性的get、set方法
- 提供无参构造方法、(试情况添加有参构造)
八、DAO数据库访问对象(Data Access Object)
- DAO实现了业务逻辑与数据库访问分离
- 对同一张表的所有操作封装在XxxDaoImpl实现类中
- 根据增删改查的不同功能实现具体的方法(insert、update、delete、selectAll)
九、Date工具类
问题:数据库存储的数据类型为java.sql.Date。而Java应用层存储日期类型为java.util.Date。用Java应用程序插入带有日期的数据到数据库时,需要进行切换
9.1 java.util.Date
- Java语言常规应用层面的日期类型,可以通过字符串创建对应的时间对象
- 无法直接通过JDBC插入到数据库
9.2 java.sql.Date
- 不可以通过字符串创建对应的时间对象,只能通过毫秒值创建对象(??? - 1970)
- 可以直接通过JDBC插入到数据库
9.3 SimpleDateFormat
格式化和解析日期的具体类。允许进行格式化(日期 -> 文本)、解析(文本 -> 日期)和规范化。
9.3.1 SimpleDateFormat应用
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");//指定日期格式
java.util.Date date = simpleDateFormat.parse(String dateStr);//将字符串解析成日期类型(java.util.Date)
String dates = simpleDateFormat.format(date);//将日期格式化转换成字符串
9.4 封装DateUtil工具类
public class DateUtil {
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
//String --> util.Date
public static java.util.Date toUtilDate(String str) {
try {
java.util.Date date = simpleDateFormat.parse(str);
return date;
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
//util.Date --> sql.Date
public static java.sql.Date toSqlDate(java.util.Date date) {
return new java.sql.Date(date.getTime());
}
//util.Date --> String
public static String toStringDate(java.util.Date date) {
return simpleDateFormat.format(date);
}
}
十、Service业务逻辑层
代表用户完成的一个业务功能,可以由一个或多个DAO的调用组成。(软件所提供的业务功能)
10.1 Service开发流程
10.2 编写Service实现转账
注:1、进行事务处理;2、回滚事务
public class AccountServiceImpl implements AccountService {
private static AccountDao accountDao = new AccountDaoImpl();
public void transfer(String fromNo, String password, String toNo, double money) {
Connection connection = null;
try {
connection = DBUtil.getConnection();//获得连接对象
connection.setAutoCommit(false);//设置当前事务的提交方式:手动提交
//验证转账账号是否存在
Account account = accountDao.select(fromNo);
if (account == null) {
throw new RuntimeException("输入账号不存在");
}
//验证转账密码是否正确
if (!account.getPassword().equals(password)) {
throw new RuntimeException("输入密码有误");
}
//验证余额是否充足
if (account.getBalance() < money) {
throw new RuntimeException("余额不足");
}
//验证对方卡号是否存在
Account targetAccount = accountDao.select(toNo);
if (targetAccount == null) {
throw new RuntimeException("对方账号不存在");
}
//减少转账用户余额
account.setBalance(account.getBalance() - money);
accountDao.update(account);
//增加对余额
targetAccount.setBalance(targetAccount.getBalance() + money);
accountDao.update(targetAccount);
connection.commit();
System.out.println("转账成功");
} catch (Exception e) {
System.out.println("转账失败");
try {
connection.rollback();//若当前程序出现异常,则回滚当前整个事务
} catch (SQLException exception) {
exception.printStackTrace();
}
e.printStackTrace();
} finally {
DBUtil.closeAll(connection, null, null);
}
}
}
十一、事务
- 在JDBC中,获得Connection对象开始事务 – 提交或回滚 – 关闭连接。事务策略:
- connection.setAutoCommint(false); //true == 1 false == 0
- connection.commit(); //手动提交事务
- connection.rollback(); //手动回滚事务
在上程序中转账出现异常后,事务控制不成功
11.1 解决方案1:传递Connection (不采用)
思考:为了解决线程中Connection对象不同步问题,是否可以将Connection对象通过service传递给各个Dao方法
11.2 传递问题
- 如果使用传递Connection,容易造成接口污染(BadSmell)
- 定义接口是为了更容易更换实现,而将Connection定义在接口中,会造成污染当前接口
11.3 解决方案2:ThreadLocal
- 可以将整个线程中(单线程)中,存储一个共享值
- 线程拥有一个类似Map的属性,键值对结构<ThreadLocal对象,值>
11.4 ThreadLocal应用
一个线程共享同一个ThreadLocal,在整个流程中任一环节可以存值或取值
11.4.1 参数绑定
在DBUtil中,将当前Connection对象添加到ThreadLocal中
public class DBUtil {
private static final Properties PROPERTIES = new Properties();
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
static {
InputStream is = DBUtil.class.getResourceAsStream("/db.properties");
try {
PROPERTIES.load(is);
Class.forName(PROPERTIES.getProperty("driver"));
} catch (IOException exception) {
exception.printStackTrace();
} catch (ClassNotFoundException exception) {
exception.printStackTrace();
}
}
public static Connection getConnection() throws ClassNotFoundException {
//将当前线程中绑定的Connection对象赋值给connection
Connection connection = threadLocal.get();
try {
if (connection == null){
connection = DriverManager.getConnection(
PROPERTIES.getProperty("url"),
PROPERTIES.getProperty("username"),
PROPERTIES.getProperty("password"));
threadLocal.set(connection);//把连接存入当前线程共享中
}
} catch (SQLException exception) {
exception.printStackTrace();
}
return connection;
}
public static void closeAll(Connection connection, Statement statement, ResultSet resultSet) {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException exception) {
exception.printStackTrace();
}
}
}
十二、事务的封装
12.1 完善工具类
public class DBUtil {
private static final Properties PROPERTIES = new Properties();
private static final ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
static {
InputStream is = DBUtil.class.getResourceAsStream("/db.properties");
try {
PROPERTIES.load(is);
Class.forName(PROPERTIES.getProperty("driver"));
} catch (IOException exception) {
exception.printStackTrace();
} catch (ClassNotFoundException exception) {
exception.printStackTrace();
}
}
/**
* 获取数据库连接
*
* @return
* @throws ClassNotFoundException
*/
public static Connection getConnection() throws ClassNotFoundException {
//将当前线程中绑定的Connection对象赋值给connection
Connection connection = threadLocal.get();
try {
if (connection == null) {
connection = DriverManager.getConnection(
PROPERTIES.getProperty("url"),
PROPERTIES.getProperty("username"),
PROPERTIES.getProperty("password"));
threadLocal.set(connection);//把连接存入当前线程共享中
}
} catch (SQLException exception) {
exception.printStackTrace();
}
return connection;
}
//开启事务
public static void startTransaction() {
try {
Connection connection = getConnection();
connection.setAutoCommit(false);
} catch (ClassNotFoundException exception) {
exception.printStackTrace();
} catch (SQLException exception) {
exception.printStackTrace();
}
}
//提交事务
public static void commitTransaction() {
Connection connection = null;
try {
connection = getConnection();
connection.commit();
} catch (ClassNotFoundException exception) {
exception.printStackTrace();
} catch (SQLException exception) {
exception.printStackTrace();
} finally {
closeAll(connection, null, null);
}
}
//回滚事务
public static void rollbackTransaction() {
Connection connection = null;
try {
connection = getConnection();
connection.rollback();
} catch (ClassNotFoundException exception) {
exception.printStackTrace();
} catch (SQLException exception) {
exception.printStackTrace();
} finally {
closeAll(connection, null, null);
}
}
/**
* 释放资源
*
* @param connection
* @param statement
* @param resultSet
*/
public static void closeAll(Connection connection, Statement statement, ResultSet resultSet) {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
threadLocal.remove();//关闭连接后,移除已关闭connection对象
}
} catch (SQLException exception) {
exception.printStackTrace();
}
}
}
十三、三层架构
13.1 三层结构
1、表现层(UI)
命名:XxxView
职责:收集用户的数据和需求、展示数据
2、业务层(事务逻辑)
命名:XxxService (接口)
impl/XxxServiceImpl(实现类)
职责:数据加工,调用DAO完成业务实现及控制事务
3、持久层(数据访问层)
命名:XxxDao(接口)
impl/XxxDaoImpl(实现类)
职责:向业务层提供数据,将业务层加工后的数据同步到数据库
13.2 三层架构项目搭建(开发步骤)
utils 存放工具类(DBUtil)
entity 存放实体类(Person)
dao 存放 Dao 接口(PersonDao)
- impl 存放 Dao 接口实现类(PersonDaoimpl)
service 存放 Service 接口(PersonService)
- impl 存放 Service 接口实现类(PersonServiceImpl)
view 存放程序启动类(main)
程序设计时,考虑易修改、易扩展,为Service层和Dao层设计接口,便于以后更换实现类
十四、DaoUtil
在持久层(Dao)层中,对数据库表的增、删、改、查等操作存在代码冗余,可对其进行抽取封装DaoUtil工具类实现复用
具体代码查看视频
:JDBC-DaoUtil
十五、Druid连接池
在程序初始化时,预先创建指定数量的数据库连接对象存储在池中。当需要连接数据库时,从连接池中取出现有连接;使用完毕后,不会关闭,而是放回池中,实现复用,节省资源
15.1 Druid连接池使用步骤
- 创建 database.properties 配置文件
- 引入 druid-1.1.5 jar 文件
15.1.1 database.properties配置文件
#连接设置
driver = com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:3306/dbname?serverTimezone=GMT&useUnicode=true&character=utf8
username = root
password = root
#<!-- 初始化连接 -->
initialSize = 10
#最大连接数量
maxActive = 50
#<!-- 最小空闲连接 -->
minIdle = 5
#<!-- 超时等待时间以毫秒为单位 1000ms = 1s -->
maxWait = 5000
15.1.2 连接池工具类
public class DBUtils {
//声明连接池对象
private static DruidDataSource druidDataSource;
static {
Properties properties = new Properties();
InputStream inputStream = DBUtilprop.class.getResourceAsStream("/database.properties");
try {
properties.load(inputStream);
//创建连接池
druidDataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
//获取连接对象
public static Connection getConnection() {
try {
return druidDataSource.getConnection();//通过连接池获取对象
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
//释放资源
...
}
十六、Apache的DbUtils使用
Commons DbUtils 是Apache组织提供的一b个对JDBC进行简单封装的开源工具类库,使用它能简化JDBC应用程序开发,同时不影响程序性能
16.1 DbUtils简介
- DbUtils是Java编程中数据库操作实用小工具:简单、小巧、实用
- 对于数据表的查询操作,可以把结果转换为List、Array、Set 等集合
- 对于数据表的 DML 操作,也变得简单(只需写SQL语句)
16.1.1 DbUtils主要包含
- ResultSetHandler 接口:转换类型接口
- BeanHandler 类:实现类 — 把一条记录转换成对象
- BeanListHandler类:实现类 — 把多条记录转换成List集合
- ScalarHandler类:实现类 — 适合获取一行一列的数据
- QueryRunner:执行sql语句类
- 增、删、改:update();
- 查:query();
具体查看官方文档
:Apache
16.2 DbUtils的使用步骤
- 导入jar包
- mysql 连接驱动jar包
- druid-1.1.5 jar
- database.properties配置文件
- common-dbutils-1.6.jar(粘贴到lib中)
mvnrepository
:mvnrepository
结尾
💻 此笔记原版出自某JDBC视频教程,找了很多JDBC的学习资料,发现这个视频蛮不错的,为了方便大家学习分享我在学习时写的笔记,内容大致一样,有些小细节可能没有写进笔记中,请见谅,如内容有误欢迎更正。
🔗视频链接👈