JDBC
Posted onepg
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JDBC相关的知识,希望对你有一定的参考价值。
JDBC
1. Jdbc概述
问题:实际开发中,不可能用工具或者命令行操作数据库,数据库表中的数据最终要使用Java程序来操作,那么Java中如何操作数据库中的数据呢?
答 : 在Java语言中,有一个专门连接数据库的规范(JDBC),专门负责连接数据库进行数据操作的规范
JDBC只是SUN编写的一堆接口(规范的体现),SUN公司自己并没有实现
问题 : 为什么SUN只定义一个JDBC规范,而不实现呢?
答 : 因为市面上的数据库很多,每个数据库内部接口不会向外暴露,而且即便是暴露让SUN去实现,市面上很多数据库全部要SUN来实现不现实
实际中哪个数据库需要支持JAVA语言,就需要自己实现Java的JDBC规范,因为实现了JDBC很多接口,那么就会有很多实现类,而很多实现类在java中会使用一个专门的包封装起来,叫做jar包(在JDBC中叫做驱动包),各大数据库产商实现JDBC规范以后都会把他们jar包放在官网上以供开发者下载使用
1.1. JDBC
JDBC(Java DataBase Connectivity): 是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基 JDBC规范对应的api包
|
2. 入门案例
2.1. 连接数据库
案例使用JDBC操作mysql数据库
2.2. 创建普通java项目
2.3. 在项目下面新建一个lib目录
2.4. 将MySQL驱动包拷贝到项目中并添加依赖
|
|
2.5. 获取数据库连接对象
准备: 1.拷贝MySQL的驱动包到项目中去:mysql-connector-java-5.1.x-bin.jar 2.build path,告速项目去哪里去找字节码文件. -------------------------------------------------------------------------------- 操作JDBC的第一步,获取JDBC的连接对象.:Connection.
步骤: 1.加载注册驱动. 就是把驱动中的Driver字节码加载到JVM中. Class.forName("com.mysql.jdbc.Driver"); 为什么这句话就可以加载注册驱动? 第一步:把com.mysql.jdbc.Driver.class这份字节码加载到JVM中. 第二步:当一份字节码被加载进JVM,马上就会执行该字节码中的静态代码块. 第三步:该静态代码中,就在完成,先创建驱动对象,再注册. 2.通过DriverManager获取连接对象. public static Connection getConnection(String url,String user,String password)
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/dbName","root","admin");
jdbc:mysql://localhost:3306/dbName jdbc:mysql:// :连接MySQL数据库的协议,不同数据库协议不一样 localhost:3306 :数据库软件的主机和端口 dbName : 具体要连接数据库 若数据库安装在本机,并且端口是默认的3306,则可以简写: Connection conn = DriverManager.getConnection("jdbc:mysql:///dbName","root","admin");
验证已经获取连接:可以在MySQL控制台,使用命令:show processlist; 查看MySQL运行进程.
|
|
public class GetConnectionDemo { public static void main(String[] args) throws Exception {
//1.加载注册驱动 : 把当前类对应的字节码加载到JVM中 Class.forName("com.mysql.jdbc.Driver"); //2.获取数据库连接 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root"); System.out.println(conn);
} } |
3. 创建表-DDL操作
在其他操作之间先要把数据库表要创建出来
创建一张t_student表: id: name: age: |
/* * * 创建表操作 * SQL : create table t_student (id int primary key auto_increment,name varchar(50),age int) */
public static void main(String[] args) throws Exception { String sql = "create table t_student (id int primary key auto_increment,name varchar(50),age int)"; //贾琏欲执事 //1,加载注册驱动 Class.forName("com.mysql.jdbc.Driver"); //2,获取数据库连接 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root"); //3,创建语句对象(用于执行SQL语句的对象) Statement st = conn.createStatement(); //4, 执行SQL语句 //int rows = st.executeUpdate(String sql);执行DDL和DML语句,放回的是受影响的行数 //ResultSet res = st.executeQuery(String sql);执行DQL查询语句,返回的结果集对象 st.executeUpdate(sql); //5,释放资源(先开后关) st.close(); conn.close(); } |
4. DML操作-表数据的增删改
//DML : 对表数据的增删改操作 public class DMLDemo {
/* * 向 t_student表中插入一条数据 * sql : insert into t_student(name,age) values (‘乔峰‘,30) */ @Test public void testInsert() throws Exception { String sql = "insert into t_student(name,age) values (‘乔峰‘,30)";
// 1.加载注册驱动 Class.forName("com.mysql.jdbc.Driver");
// 2.获取数据库连接对象 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root"); // 3.创建语句对象 Statement st = conn.createStatement();
// 4.执行SQL语句 // int rows = st.executeUpdate(String sql);执行DDL和DML语句,放回的是受影响的行数 // ResultSet res = st.executeQuery(String sql);执行DQL查询语句,返回的结果集对象 int rows = st.executeUpdate(sql); System.out.println(rows); //5.释放资源(先开后关) st.close(); conn.close();
} /* * 删除操作: 删除t_student表中的某一条数据 * SQL :delete from t_student where id = 2 */ @Test public void testDelete() throws Exception { String sql = "delete from t_student where id = 2";
// 1.加载注册驱动 Class.forName("com.mysql.jdbc.Driver");
// 2.获取数据库连接对象 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root"); // 3.创建语句对象 Statement st = conn.createStatement();
// 4.执行SQL语句 // int rows = st.executeUpdate(String sql);执行DDL和DML语句,放回的是受影响的行数 // ResultSet res = st.executeQuery(String sql);执行DQL查询语句,返回的结果集对象 int rows = st.executeUpdate(sql); System.out.println(rows); //5.释放资源(先开后关) st.close(); conn.close(); } /* * 修改操作 : 修改t_student表中的某一条数据 * SQL : update t_student set name = ‘虚竹‘,age = 50 where id = 3 */ @Test public void testUpdate() throws Exception { String sql = "update t_student set name = ‘虚竹‘,age = 50 where id = 3";
// 1.加载注册驱动 Class.forName("com.mysql.jdbc.Driver");
// 2.获取数据库连接对象 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root"); // 3.创建语句对象 Statement st = conn.createStatement();
// 4.执行SQL语句 // int rows = st.executeUpdate(String sql);执行DDL和DML语句,放回的是受影响的行数 // ResultSet res = st.executeQuery(String sql);执行DQL查询语句,返回的结果集对象 int rows = st.executeUpdate(sql); System.out.println(rows); //5.释放资源(先开后关) st.close(); conn.close(); } }
|
5. DQL操作-查询操作
5.1. 查询操作的分析
|
5.2. 查询具体操作
结果集的列的位置
|
- 使用 rs.next() 偏移光标,循环指定具体的某一行
获取数据的具体方法
getObject(int columnIndex) |
|
getObject(String columnLabel) |
package cn.sxt.jdbc._01connection;
import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; import java.util.ArrayList; import java.util.List;
import org.junit.Test;
//DQL :查询操作 public class D_DQLDemo {
/* * 多行查询 :查询t_student表中的所有数据 * SQL : select * from t_student */ @Test public void testList() throws Exception { String sql = "select * from t_student";
// 1.加载注册驱动 Class.forName("com.mysql.jdbc.Driver");
// 2.获取数据库连接对象 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root"); // 3.创建语句对象 Statement st = conn.createStatement();
// 4.执行SQL语句 // int rows = st.executeUpdate(String sql);执行DDL和DML语句,放回的是受影响的行数 // ResultSet res = st.executeQuery(String sql);执行DQL查询语句,返回的结果集对象 ResultSet rs = st.executeQuery(sql);
//创建list集合用于封装Student对象 List<Student> stus = new ArrayList<>();
while(rs.next()) { //1.通过结果集的位置获取对应的数 /*Object id = rs.getObject(1); Object name = rs.getObject(2); Object age = rs.getObject(3);*/
//2.通过结果集的 列名获取对应的数据 /*Object id = rs.getObject("id"); Object name = rs.getObject("name"); Object age = rs.getObject("age");*/ //3.通过数据库数据和Java对应的数据类型获取对应的只 int id = rs.getInt("id"); String name = rs.getString("name"); int age = rs.getInt("age"); //System.out.println(id+","+name+","+age);
//将获取的数据封装成对应的Student对象 Student stu = new Student(id, name, age);
//将一个个Student对象添加到list集合中 stus.add(stu); }
for (Student student : stus) { System.out.println(student); } //5.释放资源(先开后关) rs.close(); st.close(); conn.close(); }
/* * 单行查询: 查询出t_student 指定id的信息 * SQL : select * from t_student where id = 1; */ @Test public void testGetOne() throws Exception { String sql = "select * from t_student where id = 2";
// 1.加载注册驱动 Class.forName("com.mysql.jdbc.Driver");
// 2.获取数据库连接对象 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root"); // 3.创建语句对象 Statement st = conn.createStatement();
// 4.执行SQL语句 // int rows = st.executeUpdate(String sql);执行DDL和DML语句,放回的是受影响的行数 // ResultSet res = st.executeQuery(String sql);执行DQL查询语句,返回的结果集对象 ResultSet rs = st.executeQuery(sql);
if(rs.next()) { //1.通过结果集的位置获取对应的数 /*Object id = rs.getObject(1); Object name = rs.getObject(2); Object age = rs.getObject(3);*/
//2.通过结果集的 列名获取对应的数据 /*Object id = rs.getObject("id"); Object name = rs.getObject("name"); Object age = rs.getObject("age");*/ //3.通过数据库数据和Java对应的数据类型获取对应的只 int id = rs.getInt("id"); String name = rs.getString("name"); int age = rs.getInt("age"); //System.out.println(id+","+name+","+age);
//将获取的数据封装成对应的Student对象 Student stu = new Student(id, name, age); System.out.println(stu); } //5.释放资源(先开后关) rs.close(); st.close(); conn.close(); } }
|
6. 预编译语句对象PreparedStatment
问题 : 我们有了Statment对象可以执行SQL,为什么还要使用PreparedStatment?
优势
- SQL语句结构清晰,参数的设置和SQL语句分离
- 性能更高
- 防止SQL注入
Statement: 表示静态SQL语句对象. PreparedStatement:Statement的子接口,表示预编译SQL语句对象. 通过占位符(?)来拼SQL. |
6.1. 创建PreparedStatement
创建语句对象 Statment
createStatement() |
创建预编译语句对象PreparedStatement
prepareStatement(String sql) |
||
|
6.2. 执行SQL语句的方法
6.2.1. Statment
在执行SQL语句的时候回带上SQL语句
executeQuery(String sql) |
|
int |
executeUpdate(String sql) |
6.2.2. PreparedStatement
在执行SQL语句的方法中不需要设置SQL语句
executeQuery() |
|
int |
executeUpdate() |
6.3. 设置站位参数的值
void setXxx(int parameterIndex,Xxx value):用于设置占位符参数, parameterIndex:第几个问号. 注意:从1开始. value:设置的真实值. Xxx:表示数据类型.String/int/long/Double |
6.4. 代码
package cn.sxt.jdbc._01connection;
import static org.junit.Assert.*;
import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement;
import org.junit.Test;
//DML : 对表数据的增删改操作,使用预编译语句对象 public class E_DMLByPreparedStatmentDemo {
/* * 向 t_student表中插入一条数据 * sql : insert into t_student(name,age) values (‘乔峰‘,30) */ @Test public void testInsert() throws Exception { String sql = "insert into t_student(name,age) values (?,?)";
// 1.加载注册驱动 Class.forName("com.mysql.jdbc.Driver");
// 2.获取数据库连接对象 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root"); // 3.创建预编译语句对象 PreparedStatement ps = conn.prepareStatement(sql); //3.1设置占位符参数 ps.setString(1, "东方姑娘"); ps.setInt(2, 18);
// 4.执行SQL语句:注意不要带SQL参数 ps.executeUpdate(); //5.释放资源(先开后关) ps.close(); conn.close();
} /* * 删除操作: 删除t_student表中的某一条数据 * SQL :delete from t_student where id = 2 */ @Test public void testDelete() throws Exception { String sql = "delete from t_student where id = ?";
// 1.加载注册驱动 Class.forName("com.mysql.jdbc.Driver");
// 2.获取数据库连接对象 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root"); // 3.创建预编译语句对象 PreparedStatement ps = conn.prepareStatement(sql); //3.1设置占位符对应的参数值 ps.setInt(1, 1);
// 4.执行SQL语句 int rows = ps.executeUpdate(); System.out.println(rows); //5.释放资源(先开后关) ps.close(); conn.close(); } /* * 修改操作 : 修改t_student表中的某一条数据 * SQL : update t_student set name = ‘虚竹‘,age = 50 where id = 3 */ @Test public void testUpdate() throws Exception { String sql = "update t_student set name = ?,age = ? where id = ?";
// 1.加载注册驱动 Class.forName("com.mysql.jdbc.Driver");
// 2.获取数据库连接对象 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root"); // 3.创建预编译语句对象 PreparedStatement ps = conn.prepareStatement(sql); //3.1设置占位符参数对应的值 ps.setString(1, "西方失败"); ps.setInt(2, 40); ps.setInt(3, 4); // 4.执行SQL语句 int rows = ps.executeUpdate(); System.out.println(rows); //5.释放资源(先开后关) ps.close(); conn.close(); } }
|
7. DAO设计
实际开发中,JavaWeb开发代码一般分为三层,分层结构是JavaWeb开发中的一种设计思想,这样会让我们开发层次分明,每一层只要完成对应的功能即可,使得项目便于开发和维护
1 . Web层/表现层 : 主要接受前台浏览器用户的参数,给浏览器响应数据等等
- Service层/业务成:主要处理业务功能,日志,权限,事物,等等
- DAO层/持久层 :专门负责和数据库交互,数据处理相关代码
DAO : Data Access Object 数据访问对象
7.1. DAO思想
|
7.2. 使用DAO以后代码的以及包的设计结构
开发中如果使用的分层,编写的包和类名接口名等等都是有固定规则,不能随便瞎写
7.2.1. DAO层接口包命名
公司域名倒写+项目名称/模块名称+dao 如 : cn.sxt.crm.dao |
7.2.2. DAO层实现类包命名
公司域名倒写+项目名称/模块名称+dao+impl 如 : cn.sxt.crm.dao.impl |
7.2.3. DAO层操作对应表的接口命名
对应表的名称 + Dao/DAO
如 : StudentDao/DAO , TeacherDao/DAO |
7.2.4. DAO层操作对应表的实现类命名
对应表的名称 + Dao/DAOImpl
如 : StudentDaoImpl/DAOImpl , TeacherDaoImpl/DAOImpl |
7.2.5. 数据表对应的Java类domain/pojo包命名
POJO(Plain Ordinary Java Object)简单的Java对象
公司域名倒写+项目名称/模块名称+domain/pojo 如 : cn.sxt.crm.domain |
7.2.6. 对应的测试包命名
公司域名倒写+项目名称/模块名称+test 如 : cn.sxt.crm.test |
7.2.7. 项目的工具类包命名
公司域名倒写+项目名称/模块名称+util/utils 如 : cn.sxt.crm.util/utils |
7.2.8. DAO代码设计结构
|
7.2.9. Dao的实现类代码
package cn.sxt.jdbc.dao.impl;
import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List;
import cn.sxt.jdbc.dao.StudentDao; import cn.sxt.jdbc.domain.Student;
public class StudentDaoImpl implements StudentDao {
@Override public int saveStudent(Student stu) { String sql = "insert into t_student(name,age) values (?,?)";
Connection conn = null; PreparedStatement ps = null; try {
// 1.加载注册驱动 Class.forName("com.mysql.jdbc.Driver");
// 2.获取数据库连接对象 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root"); // 3.创建预编译语句对象 ps = conn.prepareStatement(sql); //3.1设置占位符参数 ps.setString(1, stu.getName()); ps.setInt(2, stu.getAge());
// 4.执行SQL语句:注意不要带SQL参数 return ps.executeUpdate();
} catch (Exception e) { e.printStackTrace(); }finally { //5.释放资源(先开后关) try { if(ps !=null) { ps.close(); } } catch (SQLException e) { e.printStackTrace(); }finally { try { if(conn !=null) { conn.close(); } } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } return 0; }
@Override public int deleteById(int id) {
String sql = "delete from t_student where id = ?";
Connection conn = null; PreparedStatement ps = null; try {
// 1.加载注册驱动 Class.forName("com.mysql.jdbc.Driver");
// 2.获取数据库连接对象 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root"); // 3.创建预编译语句对象 ps = conn.prepareStatement(sql); //3.1设置占位符参数 ps.setInt(1, id);
// 4.执行SQL语句:注意不要带SQL参数 return ps.executeUpdate();
} catch (Exception e) { e.printStackTrace(); }finally { //5.释放资源(先开后关) try { if(ps !=null) { ps.close(); } } catch (SQLException e) { e.printStackTrace(); }finally { try { if(conn !=null) { conn.close(); } } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } return 0; }
@Override public int updateStudentById(Student stu) {
String sql = "update t_student set name = ?,age = ? where id = ?";
Connection conn = null; PreparedStatement ps = null; try {
// 1.加载注册驱动 Class.forName("com.mysql.jdbc.Driver");
// 2.获取数据库连接对象 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root"); // 3.创建预编译语句对象 ps = conn.prepareStatement(sql); //3.1设置占位符参数 ps.setString(1, stu.getName()); ps.setInt(2, stu.getAge()); ps.setInt(3, stu.getId()); // 4.执行SQL语句:注意不要带SQL参数 return ps.executeUpdate();
} catch (Exception e) { e.printStackTrace(); }finally { //5.释放资源(先开后关) try { if(ps !=null) { ps.close(); } } catch (SQLException e) { e.printStackTrace(); }finally { try { if(conn !=null) { conn.close(); } } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } return 0; }
@Override public Student selectById(int id) { String sql = "select * from t_student where id = ?";
Connection conn = null; PreparedStatement ps = null; ResultSet rs = null;
try { // 1.加载注册驱动 Class.forName("com.mysql.jdbc.Driver");
// 2.获取数据库连接对象 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root"); // 3.创建语句对象 ps = conn.prepareStatement(sql); //3.1设置占位符参数对应的值 ps.setInt(1, id);
// 4.执行SQL语句 rs = ps.executeQuery(); if(rs.next()) { //通过数据库数据和Java对应的数据类型获取对应的只 String name = rs.getString("name"); int age = rs.getInt("age"); //System.out.println(id+","+name+","+age);
//将获取的数据封装成对应的Student对象 Student stu = new Student(id, name, age);
return stu; }
} catch (Exception e) { // TODO: handle exception }finally { try { if(rs !=null) { rs.close(); } } catch (SQLException e) { e.printStackTrace(); }finally { try { if(ps !=null) { ps.close(); } } catch (SQLException e) { e.printStackTrace(); }finally { try { if(conn !=null) { conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } } }
return null; }
@Override public List<Student> selectList() { String sql = "select * from t_student"; //创建list集合用于封装Student对象 List<Student> stus = new ArrayList<>();
Connection conn = null; PreparedStatement ps = null; ResultSet rs = null;
try {
// 1.加载注册驱动 Class.forName("com.mysql.jdbc.Driver");
// 2.获取数据库连接对象 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root"); // 3.创建语句对象 ps = conn.prepareStatement(sql);
// 4.执行SQL语句 rs = ps.executeQuery(); while(rs.next()) { //通过数据库数据和Java对应的数据类型获取对应的只 int id = rs.getInt("id"); String name = rs.getString("name"); int age = rs.getInt("age"); //System.out.println(id+","+name+","+age);
//将获取的数据封装成对应的Student对象 Student stu = new Student(id, name, age); //将一个个Student对象添加到list集合中 stus.add(stu); }
} catch (Exception e) { // TODO: handle exception }finally { try { if(rs !=null) { rs.close(); } } catch (SQLException e) { e.printStackTrace(); }finally { try { if(ps !=null) { ps.close(); } } catch (SQLException e) { e.printStackTrace(); }finally { try { if(conn !=null) { conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } } }
return stus; }
}
|
7.3. 代码初步重构
上述的DAO方法中的代码,存在的问题:
问题1:每个DAO方法中都会写:驱动名称/url/账号/密码,不利于维护. 解决方案: 声明为成员变量即可.(在被类中任何地方都可以访问)
问题2:问题1的解决方案有问题. 每个DAO实现类里都有一模一样的4行代码,不利于维护(考虑有100个DAO实现类,就得重复99次). 解决方案: 把驱动名称/url/账号/密码这四行代码,专门抽取到一个JDBC的工具类中.---->JdbcUtil. 问题3:其实DAO方法,每次操作都只想需要Connection对象即可,而不关心是如何创建的. 解决方案:把创建Connection的代码,抽取到JdbcUtil中,并提供方法getConn用于向调用者返回Connection对象即可. 问题4:每次调用者调用getConn方法的时候,都会创建一个Connection对象. 但是,每次都会加载注册驱动一次.--->没必要的. 解决方案:把加载注册驱动的代码放在静态代码块中--->只会在所在类被加载进JVM的时候,执行一次. 问题5:每个DAO方法都要关闭资源.(鸡肋代码). 解决方案:把关闭资源的代码,抽取到JdbcUtil中. public static void close(Connection conn, Statement st, ResultSet rs) {} 调用者: DML: JdbcUtil.close(conn,st,null); DQL: JdbcUtil.close(conn,st,rs); 问题6 :连接数据库的账号密码写死在JdbcUtil工具类中了,不利于维护 抽取 db.properties 配置文件,将数据库对应的账号密码写到配置文件中,然后使用程序读取配置文件内容即可
|
7.3.1. JdbcUtil工具类
package cn.sxt.jdbc.util;
import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Properties;
public class JdbcUtil {
// alt+shif+a 多行修改,修改以后还原 alt+shif+a
/*private static String driverClassName = "com.mysql.jdbc.Driver"; private static String url = "jdbc:mysql://localhost:3306/jdbcdemo"; private static String username = "root"; private static String password = "root";*/
private static Properties p = new Properties();
static { try { //1.获取类加载器 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); //2,使用类加载器获取项目 类路径下面的文件 InputStream inputStream = classLoader.getResourceAsStream("db.properties");
//3.使用Priperties加载配置文件对应的输入流 p.load(inputStream);
Class.forName(p.getProperty("driverClassName")); } catch (Exception e) { e.printStackTrace(); } }
public static Connection getConnection() { try {
return DriverManager.getConnection(p.getProperty("url"), p.getProperty("username"), p.getProperty("password")); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("亲,连接数据库失败", e); } }
public static void close(Connection conn,PreparedStatement ps,ResultSet rs) { try { if(rs !=null) { rs.close(); } } catch (SQLException e) { e.printStackTrace(); }finally { try { if(ps !=null) { ps.close(); } } catch (SQLException e) { e.printStackTrace(); }finally { try { if(conn !=null) { conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } } } } |
7.3.2. 使用工具类以后的DAO实现类效果
package cn.sxt.jdbc.dao.impl;
import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List;
import cn.sxt.jdbc.dao.StudentDao; import cn.sxt.jdbc.domain.Student; import cn.sxt.jdbc.util.JdbcUtil;
public class StudentDaoImpl implements StudentDao {
@Override public int saveStudent(Student stu) { String sql = "insert into t_student(name,age) values (?,?)";
Connection conn = null; PreparedStatement ps = null; try { conn = JdbcUtil.getConnection();
// 3.创建预编译语句对象 ps = conn.prepareStatement(sql); //3.1设置占位符参数 ps.setString(1, stu.getName()); ps.setInt(2, stu.getAge());
// 4.执行SQL语句:注意不要带SQL参数 return ps.executeUpdate();
} catch (Exception e) { e.printStackTrace(); }finally { JdbcUtil.close(conn, ps, null); } return 0; }
@Override public int deleteById(int id) {
String sql = "delete from t_student where id = ?";
Connection conn = null; PreparedStatement ps = null; try {
conn = JdbcUtil.getConnection(); // 3.创建预编译语句对象 ps = conn.prepareStatement(sql); //3.1设置占位符参数 ps.setInt(1, id);
// 4.执行SQL语句:注意不要带SQL参数 return ps.executeUpdate();
} catch (Exception e) { e.printStackTrace(); }finally { JdbcUtil.close(conn, ps, null); } return 0; }
@Override public int updateStudentById(Student stu) {
String sql = "update t_student set name = ?,age = ? where id = ?";
Connection conn = null; PreparedStatement ps = null; try {
conn = JdbcUtil.getConnection(); // 3.创建预编译语句对象 ps = conn.prepareStatement(sql); //3.1设置占位符参数 ps.setString(1, stu.getName()); ps.setInt(2, stu.getAge()); ps.setInt(3, stu.getId()); // 4.执行SQL语句:注意不要带SQL参数 return ps.executeUpdate();
} catch (Exception e) { e.printStackTrace(); }finally { JdbcUtil.close(conn, ps, null); } return 0; }
@Override public Student selectById(int id) { String sql = "select * from t_student where id = ?";
Connection conn = null; PreparedStatement ps = null; ResultSet rs = null;
try { conn = JdbcUtil.getConnection(); // 3.创建语句对象 ps = conn.prepareStatement(sql); //3.1设置占位符参数对应的值 ps.setInt(1, id);
// 4.执行SQL语句 rs = ps.executeQuery(); if(rs.next()) { //通过数据库数据和Java对应的数据类型获取对应的只 String name = rs.getString("name"); int age = rs.getInt("age"); //System.out.println(id+","+name+","+age); //将获取的数据封装成对应的Student对象 Student stu = new Student(id, name, age); return stu; }
} catch (Exception e) { // TODO: handle exception }finally { try { if(rs !=null) { rs.close(); } } catch (SQLException e) { e.printStackTrace(); }finally { JdbcUtil.close(conn, ps, rs); } }
return null; }
@Override public List<Student> selectList() { String sql = "select * from t_student"; //创建list集合用于封装Student对象 List<Student> stus = new ArrayList<>();
Connection conn = null; PreparedStatement ps = null; ResultSet rs = null;
try {
conn = JdbcUtil.getConnection(); // 3.创建语句对象 ps = conn.prepareStatement(sql);
// 4.执行SQL语句 rs = ps.executeQuery(); while(rs.next()) { //通过数据库数据和Java对应的数据类型获取对应的只 int id = rs.getInt("id"); String name = rs.getString("name"); int age = rs.getInt("age"); //System.out.println(id+","+name+","+age);
//将获取的数据封装成对应的Student对象 Student stu = new Student(id, name, age); //将一个个Student对象添加到list集合中 stus.add(stu); }
} catch (Exception e) { // TODO: handle exception }finally { JdbcUtil.close(conn, ps, rs); }
return stus; }
}
|
7.4. 知识点补充,类加载器
如何使用类加载器加载配置文件
7.4.1. 配置文件
|
7.4.1.1. 配置文件创建的位置
配置文件一般都放在项目的src 源目录下面
|
7.4.2. 加载代码
package cn.sxt.jdbc.test;
import static org.junit.Assert.*;
import java.io.InputStream; import java.util.Properties;
import org.junit.Test;
public class PropertiesTest {
@Test public void testName() throws Exception {
/* * ClassLoader 类加载器 * ClassLoader :可以从项目的类路径下面读取对应的配置文件返回一个输入流 * ClassLoader 在程序运行的时候JVM已经为每一个项目都创建了一个,我们开发者只需要获取即可 * 获取类加载器方式 * 1、使用当前线程 * ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); * 2、通过某一类的字节码实例也可以获取 * ClassLoader classLoader = PropertiesTest.class.getClassLoader(); */ ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); //使用类加载器获取项目 类路径下面的文件 InputStream inputStream = classLoader.getResourceAsStream("db.properties");
/* * Properties 是Map集合下面的一个 专门用于读取配置文件的对象 * 可以读取当前类路径下面的 xxx.properites类型的配置文件 * * xxx.properites的内容必须是key=value 键值对的数据 */
//1.创建Properties对象 Properties p = new Properties();
//2.加载配置文件 p.load(inputStream);
System.out.println(p);
//获取具体某一个key对应的值 String driverClassName = p.getProperty("driverClassName"); System.out.println(driverClassName); } }
|
7.4.3. 效果
|
8. 连接池
8.1. 遇到的问题-引出连接池
|
8.2. 连接池思想
|
8.3. 连接池的概述
在Java中,连接池使用javax.sql.DataSource接口来表示连接池.
注意:DataSource仅仅只是一个接口,由各大服务器厂商来实现(Tomcat.JBoss). 常用的DataSource的实现: DBCP: Spring推荐的 C3P0: Hibernate推荐的 Druid : 阿里巴巴开源的,性能最好,速度最快 DataSource(数据源)和连接池(Connection Pool)是同一个.
|
8.4. 使用连接池和不使用连接池的区别在哪里
从代码上: 不使用连接池: Conenction对象由DriverManager获取. Connection conn = DriverManager.getConnection(url,username,password);
使用连接池: 如何创建DataSource对象,如何在DataSource中设置url,账号,密码. Connection conn = DataSource对象.getConnection(); -------------------------------------------------------------------- 使用连接池的时候: 释放资源: Connection对象.close(): 是把Connection放回给连接池,而不是和数据库断开. |
8.5. Druid连接池的使用
8.5.1. 准备druid 连接池jar包到项目
package cn.sxt.jdbc.test;
import static org.junit.Assert.*;
import java.io.InputStream; import java.io.Reader; import java.sql.Connection; import java.util.Properties;
import javax.sql.DataSource;
import org.junit.Test;
import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.DruidDataSourceFactory; import com.alibaba.druid.pool.DruidPooledConnection;
public class DataSourceTest { // 直接创建连接池对象 @Test public void testName() throws Exception { // 1.创建连接池对象 DruidDataSource ds = new DruidDataSource(); // 2.设置连接数据库的账号密码 ds.setDriverClassName("com.mysql.jdbc.Driver"); ds.setUrl("jdbc:mysql://localhost:3306/jdbcdemo"); ds.setUsername("root"); ds.setPassword("root"); ds.setMaxActive(10);// 最大连接数 // 3.获取连接对象 Connection conn = ds.getConnection(); System.out.println(conn); }
// 使用工厂对象创建连接池对象,工厂对象的好处,不需要直接设置账号密码等等,只需要将 // 连接数据库的账号密码等等以指定的 key的名称配置到 xxx.properties文件中即可,工厂对象底层自动读取 @Test public void testDataSourceByFactory() throws Exception {
// 1.获取类加载器用于加载clsspath下面的 配置文件 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // 2.读取druid.properties配置文件 InputStream inputStream = classLoader.getResourceAsStream("druid.properties"); // 3.创建Properties对象,并读取配置文件对应的输入流 Properties p = new Properties(); p.load(inputStream);
// 4.创建连接池对象 DataSource ds = DruidDataSourceFactory.createDataSource(p); // 5.获取连接对象 Connection conn = ds.getConnection(); System.out.println(conn); } }
|
|
8.5.2. druid.propperties
|
8.5.3. 使用Druid抽取的工具类
|
9. 事务
案例:银行转账:从张无忌账户上给赵敏转1000块. 准备:account(账户表): --------------------------------------------------------------- id name(账号,唯一) balance(余额) 1 张无忌 20000 2 赵敏 0 --------------------------------------------------------------- 转账的思路: 1.检查张无忌的账号余额是否大于等于1000. SQL: SELECT balance FROM account WHERE name = ‘张无忌‘ AND balance >=1000 余额>=1000:GOTO 2: 余额 <1000:提示:亲,你的余额不足. 2.在张无忌的账号余额上减少1000. SQL: UPDATE account SET balance = balance-1000 WHERE name = ‘张无忌‘ 3.在赵敏的账户余额尚增加1000. SQL: UPDATE account SET balance = balance+1000 WHERE name = ‘赵敏‘ ------------------------------------------------------------------------------------------- 注意:在第二步和第三步之间,停电了. 使用异常模拟停电:System.out.println(1/0); |
9.1. 事务概述
事务(Transaction,简写为tx): 在数据库中,所谓事务是指一组逻辑操作单元,使数据从一种状态变换到另一种状态。 为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元: 当每个逻辑操作单元全部完成时,数据的一致性可以保持, 而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态。
事务的操作:先定义开始一个事务,然后对数据作修改操作,这时如果提交(commit),这些修改就永久地保存下来,如果回退(rollback),数据库管理系统将放弃您所作的所有修改而回到开始事务时的状态。
-------------------------------------------------- 事务的ACID属性: 1. 原子性(Atomicity) 2. 一致性(Consistency) 3. 隔离性(Isolation) 事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。 4. 持久性(Durability)
-------------------------------------------------- 事务:指构成单个逻辑工作单元的操作集合 事务处理:保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),要么整个事务回滚(rollback)到最初状态 处理事务的两个动作: 提交:commit: 当整个事务中,所有的逻辑单元都正常执行成功. ---->提交事务.---数据已经提交,不能更改. 回滚:rollback: 当整个事务中,有一个逻辑单元执行失败, ---->回滚事务. 撤销该事务中的所有操作--->恢复到最初的状态.
--------------------------------------------------------------------------------------------------- 如何在代码中去处理事务: 1.在JDBC中,事务是默认自动提交的. 必须先设置事务为手动提交. connection对象.setAutoCommit(false);//设置事务为手动提交. 2.手动的提交事务. connection对象.commit(); 3.若出现异常必须回滚事务: 不回滚事务,总余额依然是正确的. 若不回滚事务,不会释放数据库资源. connection对象.rollback(); ----------------------------------------------------------------------------------- 1.在JDBC在事务是默认提交的,那是在什么时候提交的. 在执行一个DML/DDL操作的时候,就已经提交事务了. 2.针对于CRUD操作. 只有DML操作才有事务,查询操作没有事务. 但是,我们一般会把查询也放在事务里面.
4.MySQL中,InnoDB支持外键.支持事务,MyISAM不支持外键,不支持事务.
|
9.2. 事务处理代码
public class TransactionTest { @Test public void testName() throws Exception { Connection conn = null; Statement st = null; ResultSet rs = null; try { conn = DruidUtil.getConnection(); //将事务设置为手动提交 conn.setAutoCommit(false);
st = conn.createStatement(); // 1.检查张无忌的账号余额是否大于等于1000. rs = st.executeQuery("SELECT balance FROM account WHERE name = ‘张无忌‘ AND balance >=1000"); if(!rs.next()) { throw new RuntimeException("亲,您的账户余额不够"); } // 余额>=1000:GOTO 2: // 余额 <1000:提示:亲,你的余额不足. // 2.在张无忌的账号余额上减少1000. st.executeUpdate("UPDATE account SET balance = balance-1000 WHERE name = ‘张无忌‘");
System.out.println(1/0);
// 3.在赵敏的账户余额尚增加1000. st.executeUpdate("UPDATE account SET balance = balance+1000 WHERE name = ‘赵敏‘");
//提交事务 conn.commit();
} catch (Exception e) { e.printStackTrace(); //回滚事务 conn.rollback();
}finally { DruidUtil.close(conn, st, rs); }
} }
|
以上是关于JDBC的主要内容,如果未能解决你的问题,请参考以下文章