JDBC
环境准备
1、创建project(项目)
- 先根据自身需求创建一个project路径
- 左上角File->project->Empty/project…
2、创建module(模块)
File->new->module->对话框中Java->next…
3、需要在当前module下引入需要的 jar 包
- 下载jar包
- 在当前的module下new一个Directory(目录),取名为lib
- 需要将jar包拷贝进lib文件夹中
- jar包上右键选择add as Library 点击ok
4、在当前module->src 创建package
5、创建类
引言
1、JDBC:Java DataBase Connectivity,Java数据库连接技术
注意:利用Java语言来实现数据库的客户端
2、JDBC连接数据库的方式
(1) 直接连接方式
(2) 利用连接池技术进行连接
3、JDBC中提供访问数据库的通用接口和实现类,可以访问不同的数据库(mysql、oracle、DB2等)
4、JDBC中包含两部分
(1) 接口:由sun提供,存储在jdk中,只要安装过jdk就可以使用。
(2) 实现类:由各个数据库厂商提供,将对应的实现类打成jar包放在各自的官网上,直接下载即可。
jar包的版本需要和安装的数据库服务器版本一致。
5、JDBC开发思路
数据库客户端连接数据库的步骤
- 连接数据库:输入用户名、密码
- 准备写SQL的页面(创建一个新的查询页面)
- 写sql,并运行sql (将客户端的指令发送给服务端)
- 数据库的服务端将结果返回给客户端
- 关闭窗口,释放连接,同时将数据结果清除,将所有数据库资源释放
JDBC:利用Java语言操作数据库的技术,模拟一个客户端
- 获取数据库的连接,用户名、密码,Connection对象,只有获取到Connection对象,数据库才连接成功。
- 准备发送sql的工具,获取PrepareStatement,准备sql
- 发送sql,利用发送sql工具中的工具方法完成
- 处理结果集(针对select):ResultSet
- 释放资源
JDBC的第一个程序
分为两个准备和六个步骤
1、两个准备
(1) 第一个准备:将mysql对应的JDBC依赖jar包引入当前module
(2) 第二个准备:利用navicat将库表准备好
- 在当前的连接中创建一个新的数据库,名称为bank,注意编码格式为utf-8
- 当前bank库中创建表
- t_account
2、六个步骤
-
加载驱动类
Class.forName(“com.mysql.jdbc.Driver”);
-
获取连接
String url = “jdbc:mysql://localhost:3306/bank?characterEncoding=utf-8”;
Connection conn = DriverManager.getConnection(url,“root”,“123456”);
参数说明:
url = “协议:子协议://ip地址:端口号/数据库名?characterEncoding=字符集编解码格式”;
第二个参数:连接数据库的用户名
第三个参数:用户名对应的密码
-
准备sql,同时准备发送sql的工具
String sql = “”;
PreparedStatement pstm = conn.prepareStatement(sql);
-
发送sql:
int n = pstm.executeUpdate();
-
返回值代表影响数据库行数
-
只能发送增删改insert/delete/update
ResultSet rs = pstm.executeQuery();
-
返回值代表查询到数据结果
-
只能发送 select 类型的 sql
-
处理结果集: select操作,处理 ResultSet 结果集
ResultSet的常见方法
-
rs.next():判断结果集中是否有数据,有为true,没有为false,调用一次next()会让数据的指针移动一行数据,取数据时,指针指向的这行数据会被处理
-
当 rs.next() 为true时,可以从结果集中获取对应数据:
-
Xxx 变量名 = rs.getXxx(“字段名”);
数据库中类型 Java中类型 int int double double varchar String char String int id = rs.getInt(“accId”);
String name = rs.getString(“accname”);
double balance = rs.getDouble(“balance”);
-
-
Xxx 变量 = rs.getXxx(列的编号)
注意:在指定列时,可以采用列名或是列编号。编号根据 select 后面指定的列顺序进行编号,自动的编号默认从左往右,从1开始。
注意:如果select后面是*,则字段顺序默认为表中定义的顺序,编号是根据表中字段定义的顺序。
注意:如果通过字段名获取对应字段数据时,字段有别名,需要根据别名进行获取。
-
-
释放资源:先开启的后释放,rs pstm conn
if(rs != null) rs.close();
if(pstm != null) pstm.close();
if(conn != null) conn.close();
JDBCAPI
DriverManager
DriverManager(驱动管理类)作用:
- 注册驱动
- 获取数据库连接
1、加载驱动
Class.forName("com.mysql.jdbc.Driver");
查看Driver源码
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
提示:MySQL5之后的驱动包,可以省略注册驱动的步骤,自动加载jar包中的META-INF/services/java.sql.Driver文件中的驱动类
2、获取连接
getConnection(String url, String user, String password);
参数:
1、 url:连接路径
语法:jdbc:mysql://ip地址:端口号/数据库名称?参数键值对1&参数键值对2...
细节:
-
如果连接的是本机mysql服务器,并且mysql服务默认端口为3306,则url可以简写为
jdbc:mysql:///数据库名?参数键值对1&参数键值对2...
2、user:用户名
3、password:密码
Connection
connection(数据库连接对象)作用:
- 获取执行SQL的对象
- 管理事务
1、获取执行SQL的对象
- 普通执行SQL对象
Statement createStatement()
- 预编译SQL的执行SQL对象:防止SQL注入
PreparedStatement prepareStatement(sql)
2、管理事务
开启事务:setAutoCommit(boolean autoCommit): true为自动提交事务;false为手动提交事务,即开启事务
提交事务:commit()
回滚事务:rollback()
try {
//开启事务
conn.setAutoCommit(false);
//5、执行sql
int count = stmt.executeUpdate(sql); //返回受影响的行
System.out.println(count);
int in = 3/0;
int count2 = stmt.executeUpdate(sql2);
//6、处理结果
System.out.println(count2);
//提交事务
conn.commit();
} catch (Exception throwables) {
//回滚事务
conn.rollback();
throwables.printStackTrace();
}
使用此,可以让两条sql都更改不成功
ResultSet
封装了DQL查询语句的结果
ResultSet stmt.executeQuery(sql):执行DQL语句,返回ResultSet对象
获取查询结果
boolean next():(1)将光标从当前位置向前移动一行(2) 判断当前行是否为有效行
返回值:
- true:有效行,当前行有数据
- false:无效行,当前行没有数据
xxx getXxx(参数):获取数据
xxx:数据类型,如int getInt(参数) String getString(参数)
参数:
- int:列的编号,从1开始
- String:列的名称
ResultSet使用步骤
1、游标向下移动一行,并判断该行是否有数据:next()
2、获取数据:getXxx(参数)
//循环判断游标是否是最后一行末尾
while(rs.next()){
//获取数据
rs.getXxx(参数);
}
PreparedStatement
- 获取PreparedStatement对象
// SQL语句中的参数值,使用?占位符替代
String sql = "select * from user where username = ? and pssword = ?";
// 通过Connection 对象获取,并传入对应的sql语句
PreparedStatement pstm = conn.prepareStatment(sql);
- 设置参数值
PreparedStatement对象:setXxx(参数1,参数2):给?赋值
Xxx:数据类型
参数:参数1:?的位置编号,从1开始
参数2:?的值
1、占位符:?,只能代表某一个数值,只能为数据占位,不能为字段占位。提高代码灵活度。
"select * form t_account where id=?"
2、为?绑定参数,一定在发送sql之前
-
为每一个?设置一个编号,从1开始,从左往右依次为1、2、3.。。
-
在发送sql之前,完成?占位符的数据绑定
pstm.setXxx(?编号, 值);
Xxx代表数据类型,取决于 ?对应的字段数据类型,对应关系:
数据库中字段类型 Java中数据类型 int int double double char/varchar String pstm.setInt(编号,int类型数值);
pstm.setInt(编号,double类型数值);
pstm.setInt(编号,String类型数值);
PreparedStatement 好处
- 预编译SQL,性能更高
- 防止SQL注入:将敏感字符进行转义
SQL注入
SQL注入是通过操作输入来修改实现定义好的SQL语句,用以达到执行代码对服务器进行攻击的方法。
SQL注入问题:
'or '1'= '1
password = ''or '1'= '1'
PreparedStatement会帮我们把字符转义,就解决了sql注入的问题
password = '\'or \'1\'= \'1\'
PreparedStatement预编译功能开启:useServerPrepStmts=true
配置MySQL执行日志(重启sql服务后生效)将下列部分添加到 my.in 目录下
log-output=FILE
general-log=1
general_log_file="D:\mysql.log"
slow-query-log=1
slow_query_log_file="D:\mysql_slow.log"
long_query_time=2
查看D盘中的日志文件,即可看到编译期已经进行转义,防止sql注入了。
当我们开启预编译
查看日志文件
可以看出获取pstm对象的时候,就已经预编译了,执行时直接执行,预编译只进行一次。
PreparedStatement原理总结
- 在获取PreparedStatement对象时,将sql语句发送给mysql服务器进行检查,编译(这些步骤很耗时)
- 执行时就不用进行这些步骤了,速度很快。
- 如果sql模板一样,只需要进行一次检查、编译。
JDBC开发步骤
- 加载驱动类
- 获取连接
- 准备sql,同时准备发送sql的工具
- 绑定参数,再发送sql
- 处理结果集
- 释放资源
JDBCUtil工具类
第一个版本
-
定义一个JDBCUtil类
-
类中提供两个静态方法
- 获取连接方法:1、加载驱动类 2、获取连接
- 释放资源方法:释放资源
-
第一个版本解决了代码冗余问题,将JDBC中六个步骤中重复的步骤进行提取。
-
存在问题:没有解决因数据库发生改变而带来的工具类的变化,不够灵活。
public class JDBCUtil {
private JDBCUtil() {
}
//获取连接
public static Connection getConnection() {
try {
//加载驱动类
//获取连接
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/bank?characterEncoding=utf-8";
Connection conn = DriverManager.getConnection(url, "root", "791345");
return conn;
} catch (Exception e) {
e.printStackTrace();
//讲出的异常转化成运行时异常通知调用者
throw new RuntimeException("获取连接失败啦", e);
}
}
public static void close(ResultSet rs, PreparedStatement pstm, Connection conn) {
//释放资源
try {
if (rs != null) {
rs.close();
}
if (pstm != null) {
pstm.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
throw new RuntimeException("关闭资源出错啦!", throwables);
}
}
}
第二个版本
1、解决:数据库不同 参数需要改变,提高灵活性、通用性
2、在当前module中创建 一个包
/com/baizhi/hjq/conf
创建一个配置文件db.properties,配置文件中内容
3、利用Properties,是Map的实现类,key和value默认为String类型。
load(输入流):自动将输入流指向文件中内容加载读取到properties中,自动以=做拆分,=左边为key,=右边为value,每一行对应一个key-value
注意:使用类加载流
InputStream in = JDBCUtil1.class.getResourceAsStream("/com/baizhi/hjq/conf/db.properties");
写路径时以/开头,以module的src下一级目录开始写,到properties文件所在的路径即可,中间都是以/隔开
getProperties(key):根据键获取对应的value。
public class JDBCUtil {
private JDBCUtil() {
}
//获取连接
public static Connection getConnection() {
try {
//加载驱动类
//获取连接
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/bank?characterEncoding=utf-8";
Connection conn = DriverManager.getConnection(url, "root", "791345");
return conn;
} catch (Exception e) {
e.printStackTrace();
//讲出的异常转化成运行时异常通知调用者
throw new RuntimeException("获取连接失败啦", e);
}
}
public static void close(ResultSet rs, PreparedStatement pstm, Connection conn) {
//释放资源
try {
if (rs != null) {
rs.close();
}
if (pstm != null) {
pstm.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
throw new RuntimeException("关闭资源出错啦!", throwables);
}
}
}
第三版本
1、解决效率问题
2、将读取配置文件的内容,定义在静态代码块中,在类加载时执行一次即可。
public class JDBCUtil1 {
private static final Properties pro = new Properties();
private JDBCUtil1() {
}
static{
//将配置文件加载到Java程序中
try {
InputStream in = JDBCUtil1.class.getResourceAsStream("/com/baizhi/hjq/conf/db.properties");
pro.load(in);
} catch (IOException e) {
e.printStackTrace();
//讲出的异常转化成运行时异常通知调用者
throw new RuntimeException("获取连接失败啦", e);
}
}
//获取连接
public static Connection getConnection() {
try {
String driver = pro.getProperty("mydriver");
String url = pro.getProperty("url");
String username = pro.getProperty("username");
String password = pro.getProperty("password");
//加载驱动类
Class.forName(driver);
//创建连接
Connection conn = DriverManager.getConnection(url, username, password);
return conn;
} catch (Exception e) {
e.printStackTrace();
//讲出的异常转化成运行时异常通知调用者
throw new RuntimeException("获取连接失败啦", e);
}
}
public static void close(ResultSet rs, PreparedStatement pstm, Connection conn) {
//释放资源
try {
if (rs != null) {
rs.close();
}
if (pstm != null) {
pstm.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
throw new RuntimeException("关闭资源出错啦!", throwables);
}
}
}
开发步骤:
- 1-2通过工具类获取连接
- 3、准备sql,同时准备发送sql的工具
- 绑定参数,同时发送sql
- 处理结果集
- 通过工具类JDBCUtil释放资源
Dao
1、Dao:Data Access Object(数据访问对象)
2、dao中需要封装对一张表的所有操作,例如:增删改查
3、Dao类设计思想:
-
通常利用一个Dao类对应数据库表中的一张表。
-
表中不同 sql 操作,对应类中不同的功能方法。
-
通常对表中增删改查操作需求,对应Dao类中的方法。
-
dao类中方法功能要单一。
-
每一个方法的开发步骤:6步骤(jdbc开发步骤)。
-
4、Dao类命名要求
(1)数据库中一张表通常对应一个Dao类
(2)类名要求:表名+Dao 类名首字母大写,驼峰命名,去除_。
注意:Java命名里,不要有 _!!!!
(3)dao类存储的位置:com.baizhi.hjq.dao包下
(4)dao中的每个方法出现的sql必须在navicat中先进行测试
ORM
(1)ORM:object Relational Mapping 对象映射关系
Java中一个类对应数据库一张表(数据库中一行数据对应Java中一个对象)
(2)实体类:和表映射对应的类,称为实体类。
(3)实体类的要求:
-
类名取决于对应的表名,通常是将对应的表的首字母大写作为类名
数据库中库表 Java中实体类名 students Student t_account Account -
实体类存储在com.baizhi.hjq.entity包中
-
实体类要求实现Serializable接口
-
实体类中的属性取决于对应表的字段:个数、类型、名字
表中字段 | Java中属性 |
---|---|
acc_id int | Integer accId |
accName char | String accName |
- 属性私有化,提供公开的get/set方法
- 提供两个构造方法:无参数和有参数的构造方法
- 重写toString() 方便展示信息
6、dao中常见方法
class 表名+Dao{
//删除
public void deleteById(int id){}
//根据id查询
public Account selectOne(int id){}
//查询所有
public List<Account> queryAll(){}
}
业务层
1、业务:代表根据用户需求完成一个对用功能,通常由一次或多次调用dao中方法完成。
2、业务类:专注于对用户的业务功能进行处理,一个业务对应业务类中的一个方法。
- 业务类:望文生义,XxxService,例如AccountService、BankService
- 业务类:存储在com.baizhi.hjq.service包中
- 业务类中的每一个方法代表一个业务功能,方法名望文生义即可。
- 完成业务功能时,需要控制事务:
- 事务:是由一条sql或是多条sql组成的,所有sql都执行成功,则需要提交事务(commit)只要有一条sql执行失败,则回滚事务(rollback)
- 事务由业务决定,一个业务对应一个事务,业务大小决定了事务中sql的组成。
- 利用jdbc中的事务控制,保证事务原子性
3、JDBC中事务控制的API
-
目前事务控制失败的原因:JDBC中默认自动提交事务,执行1条sql就提交一次事务
-
控制事务常见的方法,位于Connection中:
//获取链接 Connection conn = JDBCUtil.getConnection(); //将事务控制的方式设置成手动提交 conn.setAutoCommit(false); //提交事务 conn.commit(); //事务的回滚 conn.rollback();
业务中控制事务的模板
public 返回值 方法名(形参){
Connection conn = null;
try{
//1、通过工具类JDBCUtil获取链接
conn = JDBCUtil.getConnection();
//2、设置事务提交方式为手动
conn.setAutoCommit(false);
//3、调用dao方法完成业务功能
//4、提交事务
conn.commit();
}catch(Exception e){
//4、回滚事务
conn.rollback();
}finally{
//5、释放资源(只需要释放连接)
JDBCUtil.close(null,null,conn);
}
}
4、事务控制失败的原因
业务层service调用dao层的方法来实现对应的业务,业务层 dao 都是通过 JDBCUtil获取链接(service层和dao层获取的是不同链接),在service层中将提交方式设置成手动提交,但是dao层的连接的提交方式依然为自动提交,service事务控制无法控制dao层。dao层执行一条sql,默认提交,导致service层事务回滚时失败。
5、解决思路:需要保证一个业务中的service和dao层获取同一个连接
6、ThreadLocal:利用ThreadLocal解决service和dao层连接不统一的问题
ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该
变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。
ThreadLocal 变量通常被private static修饰。
当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。
ThreadLocal理解为:存储连接conn到容器
-
ThreadLocal常见的方法:
ThreadLocal<Connection> tl = new ThreadLocal(); Connection conn = tl.get(); //获取链接 tl.set(conn); //将连接存储在ThreadLocal tl.remove(); //清楚tl中的连接
7、JDBCUtil最终版本
public class JDBCUtil {
private static final Properties pro = new Properties();
private static final ThreadLocal<Connection> tl = new ThreadLocal();
private JDBCUtil() {
}
static{
//将配置文件加载到Java程序中
try {
InputStream in = JDBCUtil.class.getResourceAsStream("/com/baizhi/hjq/conf/db.properties");
pro.load(in);
} catch (IOException e) {
e.printStackTrace();
//讲出的异常转化成运行时异常通知调用者
throw new RuntimeException("获取连接失败啦", e);
}
}
//获取连接
public static Connection getConnection() {
try {
Connection conn = tl.get(); //从ThreadLocal容器中获取链接
if(conn == null) {
String driver = pro.getProperty("mydriver");
String url = pro.getProperty("url");
String username = pro.getProperty("username");
String password = pro.getProperty("password");
//加载驱动类
Class.forName(driver);
//创建连接
conn = DriverManager.getConnection(url, username, password);
//将获取的连接存储在threadLocal容器中
tl.set(conn);
}
return conn;
} catch (Exception e) {
e.printStackTrace();
//讲出的异常转化成运行时异常通知调用者
throw new RuntimeException("获取连接失败啦", e);
}
}
public static void close(ResultSet rs, PreparedStatement pstm, Connection conn) {
//释放资源
try {
if (rs != null) {
rs.close();
}
if (pstm != null) {
pstm.close();
}
if (conn != null) {
conn.close();
tl.remove();//将连接从threadLocal容器中清除掉
}
} catch (SQLException throwables) {
throwables.printStackTrace();
throw new RuntimeException("关闭资源出错", throwables);
}
}
}
注意:在dao层不要关闭conn连接,service层才连接。
JDBC中分包分层
1、JDBC中分成三层
- dao层:数据访问层,专注于数据库的增删改查。dao层分为6个步骤,不能关闭连接
- service层:业务层,专注于实现用户业务需求,需要控制事务。service层分为5步。
- view层:视图层,专注于给用户做展示的,同时接受用户输入的信息
注意:方法调用过程中,必须逐级调用,不能跨层调用
2、分层的好处
(1) 各司其职,降低代码耦合度
(2) 便于分工,利于团队的协同开发
3、JDBC的分包
com.baizhi.hjq.conf:存储配置文件
com.baizhi.hjq.util:存储工具类,例如 JDBCUtil
------先测试连接-----
com.baizhi.hjq.test:测试存储内容
com.baizhi.hjq.sql:存储sql文件
com.baizhi.hjq.entity:存储实体类
com.baizhi.hjq.dao:先接口 再实现(接口:XxxDao 实现类“XxxDaoImpl“)
注意:dao中每个方法必须单独测试