JDBC-用Java语句操作数据库
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.java连接数据库
以连接mysql为例
2.1. 创建普通java项目
2.2. 在项目下面新建一个lib目录
2.3. 将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运行进程.
1 public class GetConnectionDemo { 2 public static void main(String[] args) throws Exception { 3 4 //1.加载注册驱动 : 把当前类对应的字节码加载到JVM中 5 Class.forName("com.mysql.jdbc.Driver"); 6 //2.获取数据库连接 7 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root"); 8 System.out.println(conn); 9 10 } 11 }
3. 创建表-DDL操作
在其他操作之间先要把数据库表要创建出来
/贾琏欲执事
创建一张t_student表:
id:
name:
age:
1 //DML : 对表数据的增删改操作 2 public class DMLDemo { 3 4 /* 5 * 向 t_student表中插入一条数据 6 * sql : insert into t_student(name,age) values ('乔峰',30) 7 */ 8 @Test 9 public void testInsert() throws Exception { 10 String sql = "insert into t_student(name,age) values ('乔峰',30)"; 11 12 // 1.加载注册驱动 13 Class.forName("com.mysql.jdbc.Driver"); 14 15 // 2.获取数据库连接对象 16 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root"); 17 // 3.创建语句对象 18 Statement st = conn.createStatement(); 19 20 // 4.执行SQL语句 21 // int rows = st.executeUpdate(String sql);执行DDL和DML语句,放回的是受影响的行数 22 // ResultSet res = st.executeQuery(String sql);执行DQL查询语句,返回的结果集对象 23 int rows = st.executeUpdate(sql); 24 System.out.println(rows); 25 //5.释放资源(先开后关) 26 st.close(); 27 conn.close(); 28 29 } 30 /* 31 * 删除操作: 删除t_student表中的某一条数据 32 * SQL :delete from t_student where id = 2 33 */ 34 @Test 35 public void testDelete() throws Exception { 36 String sql = "delete from t_student where id = 2"; 37 38 // 1.加载注册驱动 39 Class.forName("com.mysql.jdbc.Driver"); 40 41 // 2.获取数据库连接对象 42 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root"); 43 // 3.创建语句对象 44 Statement st = conn.createStatement(); 45 46 // 4.执行SQL语句 47 // int rows = st.executeUpdate(String sql);执行DDL和DML语句,放回的是受影响的行数 48 // ResultSet res = st.executeQuery(String sql);执行DQL查询语句,返回的结果集对象 49 int rows = st.executeUpdate(sql); 50 System.out.println(rows); 51 //5.释放资源(先开后关) 52 st.close(); 53 conn.close(); 54 } 55 /* 56 * 修改操作 : 修改t_student表中的某一条数据 57 * SQL : update t_student set name = '虚竹',age = 50 where id = 3 58 */ 59 @Test 60 public void testUpdate() throws Exception { 61 String sql = "update t_student set name = '虚竹',age = 50 where id = 3"; 62 63 // 1.加载注册驱动 64 Class.forName("com.mysql.jdbc.Driver"); 65 66 // 2.获取数据库连接对象 67 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root"); 68 // 3.创建语句对象 69 Statement st = conn.createStatement(); 70 71 // 4.执行SQL语句 72 // int rows = st.executeUpdate(String sql);执行DDL和DML语句,放回的是受影响的行数 73 // ResultSet res = st.executeQuery(String sql);执行DQL查询语句,返回的结果集对象 74 int rows = st.executeUpdate(sql); 75 System.out.println(rows); 76 //5.释放资源(先开后关) 77 st.close(); 78 conn.close(); 79 } 80 }
4.DML操作-表数据的增删改
1 //DML : 对表数据的增删改操作 2 public class DMLDemo { 3 4 /* 5 * 向 t_student表中插入一条数据 6 * sql : insert into t_student(name,age) values ('乔峰',30) 7 */ 8 @Test 9 public void testInsert() throws Exception { 10 String sql = "insert into t_student(name,age) values ('乔峰',30)"; 11 12 // 1.加载注册驱动 13 Class.forName("com.mysql.jdbc.Driver"); 14 15 // 2.获取数据库连接对象 16 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root"); 17 // 3.创建语句对象 18 Statement st = conn.createStatement(); 19 20 // 4.执行SQL语句 21 // int rows = st.executeUpdate(String sql);执行DDL和DML语句,放回的是受影响的行数 22 // ResultSet res = st.executeQuery(String sql);执行DQL查询语句,返回的结果集对象 23 int rows = st.executeUpdate(sql); 24 System.out.println(rows); 25 //5.释放资源(先开后关) 26 st.close(); 27 conn.close(); 28 29 } 30 /* 31 * 删除操作: 删除t_student表中的某一条数据 32 * SQL :delete from t_student where id = 2 33 */ 34 @Test 35 public void testDelete() throws Exception { 36 String sql = "delete from t_student where id = 2"; 37 38 // 1.加载注册驱动 39 Class.forName("com.mysql.jdbc.Driver"); 40 41 // 2.获取数据库连接对象 42 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root"); 43 // 3.创建语句对象 44 Statement st = conn.createStatement(); 45 46 // 4.执行SQL语句 47 // int rows = st.executeUpdate(String sql);执行DDL和DML语句,放回的是受影响的行数 48 // ResultSet res = st.executeQuery(String sql);执行DQL查询语句,返回的结果集对象 49 int rows = st.executeUpdate(sql); 50 System.out.println(rows); 51 //5.释放资源(先开后关) 52 st.close(); 53 conn.close(); 54 } 55 /* 56 * 修改操作 : 修改t_student表中的某一条数据 57 * SQL : update t_student set name = '虚竹',age = 50 where id = 3 58 */ 59 @Test 60 public void testUpdate() throws Exception { 61 String sql = "update t_student set name = '虚竹',age = 50 where id = 3"; 62 63 // 1.加载注册驱动 64 Class.forName("com.mysql.jdbc.Driver"); 65 66 // 2.获取数据库连接对象 67 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root"); 68 // 3.创建语句对象 69 Statement st = conn.createStatement(); 70 71 // 4.执行SQL语句 72 // int rows = st.executeUpdate(String sql);执行DDL和DML语句,放回的是受影响的行数 73 // ResultSet res = st.executeQuery(String sql);执行DQL查询语句,返回的结果集对象 74 int rows = st.executeUpdate(sql); 75 System.out.println(rows); 76 //5.释放资源(先开后关) 77 st.close(); 78 conn.close(); 79 } 80 }
5. DQL操作-查询操作
5.1. 查询操作的分析
5.2 查询具体操作
结果集的列的位置
1.使用 rs.next() 偏移光标,循环指定具体的某一行
获取数据的具体方法
getObject(int columnIndex) |
|
getObject(String columnLabel)
|
1 package cn.sxt.jdbc._01connection; 2 3 4 import java.sql.Connection; 5 import java.sql.DriverManager; 6 import java.sql.ResultSet; 7 import java.sql.Statement; 8 import java.util.ArrayList; 9 import java.util.List; 10 11 import org.junit.Test; 12 13 //DQL :查询操作 14 public class D_DQLDemo { 15 16 /* 17 * 多行查询 :查询t_student表中的所有数据 18 * SQL : select * from t_student 19 */ 20 @Test 21 public void testList() throws Exception { 22 String sql = "select * from t_student"; 23 24 // 1.加载注册驱动 25 Class.forName("com.mysql.jdbc.Driver"); 26 27 // 2.获取数据库连接对象 28 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root"); 29 // 3.创建语句对象 30 Statement st = conn.createStatement(); 31 32 // 4.执行SQL语句 33 // int rows = st.executeUpdate(String sql);执行DDL和DML语句,放回的是受影响的行数 34 // ResultSet res = st.executeQuery(String sql);执行DQL查询语句,返回的结果集对象 35 ResultSet rs = st.executeQuery(sql); 36 37 38 //创建list集合用于封装Student对象 39 List<Student> stus = new ArrayList<>(); 40 41 while(rs.next()) { 42 //1.通过结果集的位置获取对应的数 43 /*Object id = rs.getObject(1); 44 Object name = rs.getObject(2); 45 Object age = rs.getObject(3);*/ 46 47 //2.通过结果集的 列名获取对应的数据 48 /*Object id = rs.getObject("id"); 49 Object name = rs.getObject("name"); 50 Object age = rs.getObject("age");*/ 51 //3.通过数据库数据和Java对应的数据类型获取对应的只 52 int id = rs.getInt("id"); 53 String name = rs.getString("name"); 54 int age = rs.getInt("age"); 55 //System.out.println(id+","+name+","+age); 56 57 //将获取的数据封装成对应的Student对象 58 Student stu = new Student(id, name, age); 59 60 //将一个个Student对象添加到list集合中 61 stus.add(stu); 62 } 63 64 for (Student student : stus) { 65 System.out.println(student); 66 } 67 //5.释放资源(先开后关) 68 rs.close(); 69 st.close(); 70 conn.close(); 71 } 72 73 74 /* 75 * 单行查询: 查询出t_student 指定id的信息 76 * SQL : select * from t_student where id = 1; 77 */ 78 @Test 79 public void testGetOne() throws Exception { 80 String sql = "select * from t_student where id = 2"; 81 82 // 1.加载注册驱动 83 Class.forName("com.mysql.jdbc.Driver"); 84 85 // 2.获取数据库连接对象 86 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root"); 87 // 3.创建语句对象 88 Statement st = conn.createStatement(); 89 90 // 4.执行SQL语句 91 // int rows = st.executeUpdate(String sql);执行DDL和DML语句,放回的是受影响的行数 92 // ResultSet res = st.executeQuery(String sql);执行DQL查询语句,返回的结果集对象 93 ResultSet rs = st.executeQuery(sql); 94 95 if(rs.next()) { 96 //1.通过结果集的位置获取对应的数 97 /*Object id = rs.getObject(1); 98 Object name = rs.getObject(2); 99 Object age = rs.getObject(3);*/ 100 101 //2.通过结果集的 列名获取对应的数据 102 /*Object id = rs.getObject("id"); 103 Object name = rs.getObject("name"); 104 Object age = rs.getObject("age");*/ 105 //3.通过数据库数据和Java对应的数据类型获取对应的只 106 int id = rs.getInt("id"); 107 String name = rs.getString("name"); 108 int age = rs.getInt("age"); 109 //System.out.println(id+","+name+","+age); 110 111 //将获取的数据封装成对应的Student对象 112 Student stu = new Student(id, name, age); 113 System.out.println(stu); 114 } 115 //5.释放资源(先开后关) 116 rs.close(); 117 st.close(); 118 conn.close(); 119 } 120 }
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/Date |
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. JavaWeb开发的分层设计-三层架构
7.1. DAO层设计
实际开发中,JavaWeb开发代码一般分为三层,分层结构是JavaWeb开发中的一种设计思想,这样会让我们开发层次分明,每一层只要完成对应的功能即可,使得项目便于开发和维护
1 . Web层/表现层 : 主要接受前台浏览器用户的参数,给浏览器响应数据等等
- Service层/业务成/服务层:主要处理业务功能,日志,权限,事物,等等
- DAO层/持久层 :专门负责和数据库交互,数据处理相关代码
DAO : Data Access Object 数据访问对象
实际开发中 : 用户请求到-Web层--->Service层-->DAO层
7.2. DAO思想
|
7.3. 使用DAO以后代码的以及包的设计结构
开发中如果使用的分层,编写的包和类名接口名等等都是有固定规则,不能随便瞎写
7.3.1. DAO层接口包命名
公司域名倒写+项目名称/模块名称+dao 如 : cn.sxt.crm.dao |
7.3.2. DAO层实现类包命名
公司域名倒写+项目名称/模块名称+dao+impl 如 : cn.sxt.crm.dao.impl |
7.3.3. DAO层操作对应表的接口命名
对应表的名称 + Dao/DAO
如 : StudentDao/DAO , TeacherDao/DAO |
7.3.4. DAO层操作对应表的实现类命名
对应表的名称 + Dao/DAOImpl
如 : StudentDaoImpl/DAOImpl , TeacherDaoImpl/DAOImpl |
7.3.5. 数据表对应的Java类domain/pojo包命名
POJO(Plain Ordinary Java Object)简单的Java对象
domian : 域对象
公司域名倒写+项目名称/模块名称+domain/pojo 如 : cn.sxt.crm.domain |
7.3.6. 对应的测试包命名
公司域名倒写+项目名称/模块名称+test 如 : cn.sxt.crm.test |
7.3.7. 项目的工具类包命名
公司域名倒写+项目名称/模块名称+util/utils 如 : cn.sxt.crm.util/utils |
7.3.8. DAO代码设计结构
7.3.9. Dao的实现类代码
1 package cn.sxt.jdbc.dao.impl; 2 3 import java.sql.Connection; 4 import java.sql.DriverManager; 5 import java.sql.PreparedStatement; 6 import java.sql.ResultSet; 7 import java.sql.SQLException; 8 import java.util.ArrayList; 9 import java.util.List; 10 11 import cn.sxt.jdbc.dao.StudentDao; 12 import cn.sxt.jdbc.domain.Student; 13 14 public class StudentDaoImpl implements StudentDao { 15 16 @Override 17 public int saveStudent(Student stu) { 18 String sql = "insert into t_student(name,age) values (?,?)"; 19 20 Connection conn = null; 21 PreparedStatement ps = null; 22 try { 23 24 // 1.加载注册驱动 25 Class.forName("com.mysql.jdbc.Driver"); 26 27 // 2.获取数据库连接对象 28 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root"); 29 // 3.创建预编译语句对象 30 ps = conn.prepareStatement(sql); 31 //3.1设置占位符参数 32 ps.setString(1, stu.getName()); 33 ps.setInt(2, stu.getAge()); 34 35 // 4.执行SQL语句:注意不要带SQL参数 36 return ps.executeUpdate(); 37 38 39 } catch (Exception e) { 40 e.printStackTrace(); 41 }finally { 42 //5.释放资源(先开后关) 43 try { 44 if(ps !=null) { 45 ps.close(); 46 } 47 } catch (SQLException e) { 48 e.printStackTrace(); 49 }finally { 50 try { 51 if(conn !=null) { 52 conn.close(); 53 } 54 } catch (SQLException e) { 55 // TODO Auto-generated catch block 56 e.printStackTrace(); 57 } 58 } 59 } 60 return 0; 61 } 62 63 @Override 64 public int deleteById(int id) { 65 66 String sql = "delete from t_student where id = ?"; 67 68 Connection conn = null; 69 PreparedStatement ps = null; 70 try { 71 72 // 1.加载注册驱动 73 Class.forName("com.mysql.jdbc.Driver"); 74 75 // 2.获取数据库连接对象 76 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root"); 77 // 3.创建预编译语句对象 78 ps = conn.prepareStatement(sql); 79 //3.1设置占位符参数 80 ps.setInt(1, id); 81 82 // 4.执行SQL语句:注意不要带SQL参数 83 return ps.executeUpdate(); 84 85 86 } catch (Exception e) { 87 e.printStackTrace(); 88 }finally { 89 //5.释放资源(先开后关) 90 try { 91 if(ps !=null) { 92 ps.close(); 93 } 94 } catch (SQLException e) { 95 e.printStackTrace(); 96 }finally { 97 try { 98 if(conn !=null) { 99 conn.close(); 100 } 101 } catch (SQLException e) { 102 // TODO Auto-generated catch block 103 e.printStackTrace(); 104 } 105 } 106 } 107 return 0; 108 } 109 110 @Override 111 public int updateStudentById(Student stu) { 112 113 String sql = "update t_student set name = ?,age = ? where id = ?"; 114 115 Connection conn = null; 116 PreparedStatement ps = null; 117 try { 118 119 // 1.加载注册驱动 120 Class.forName("com.mysql.jdbc.Driver"); 121 122 // 2.获取数据库连接对象 123 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root"); 124 // 3.创建预编译语句对象 125 ps = conn.prepareStatement(sql); 126 //3.1设置占位符参数 127 ps.setString(1, stu.getName()); 128 ps.setInt(2, stu.getAge()); 129 ps.setInt(3, stu.getId()); 130 // 4.执行SQL语句:注意不要带SQL参数 131 return ps.executeUpdate(); 132 133 134 } catch (Exception e) { 135 e.printStackTrace(); 136 }finally { 137 //5.释放资源(先开后关) 138 try { 139 if(ps !=null) { 140 ps.close(); 141 } 142 } catch (SQLException e) { 143 e.printStackTrace(); 144 }finally { 145 try { 146 if(conn !=null) { 147 conn.close(); 148 } 149 } catch (SQLException e) { 150 // TODO Auto-generated catch block 151 e.printStackTrace(); 152 } 153 } 154 } 155 return 0; 156 } 157 158 @Override 159 public Student selectById(int id) { 160 String sql = "select * from t_student where id = ?"; 161 162 Connection conn = null; 163 PreparedStatement ps = null; 164 ResultSet rs = null; 165 166 try { 167 // 1.加载注册驱动 168 Class.forName("com.mysql.jdbc.Driver"); 169 170 // 2.获取数据库连接对象 171 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root"); 172 // 3.创建语句对象 173 ps = conn.prepareStatement(sql); 174 //3.1设置占位符参数对应的值 175 ps.setInt(1, id); 176 177 // 4.执行SQL语句 178 rs = ps.executeQuery(); 179 if(rs.next()) { 180 //通过数据库数据和Java对应的数据类型获取对应的只 181 String name = rs.getString("name"); 182 int age = rs.getInt("age"); 183 //System.out.println(id+","+name+","+age); 184 185 //将获取的数据封装成对应的Student对象 186 Student stu = new Student(id, name, age); 187 188 return stu; 189 } 190 191 } catch (Exception e) { 192 // TODO: handle exception 193 }finally { 194 try { 195 if(rs !=null) { 196 rs.close(); 197 } 198 } catch (SQLException e) { 199 e.printStackTrace(); 200 }finally { 201 try { 202 if(ps !=null) { 203 ps.close(); 204 } 205 } catch (SQLException e) { 206 e.printStackTrace(); 207 }finally { 208 try { 209 if(conn !=null) { 210 conn.close(); 211 } 212 } catch (SQLException e) { 213 e.printStackTrace(); 214 } 215 } 216 } 217 } 218 219 return null; 220 } 221 222 @Override 223 public List<Student> selectList() { 224 String sql = "select * from t_student"; 225 //创建list集合用于封装Student对象 226 List<Student> stus = new ArrayList<>(); 227 228 Connection conn = null; 229 PreparedStatement ps = null; 230 ResultSet rs = null; 231 232 try { 233 234 // 1.加载注册驱动 235 Class.forName("com.mysql.jdbc.Driver"); 236 237 // 2.获取数据库连接对象 238 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root"); 239 // 3.创建语句对象 240 ps = conn.prepareStatement(sql); 241 242 // 4.执行SQL语句 243 rs = ps.executeQuery(); 244 while(rs.next()) { 245 //通过数据库数据和Java对应的数据类型获取对应的只 246 int id = rs.getInt("id"); 247 String name = rs.getString("name"); 248 int age = rs.getInt("age"); 249 //System.out.println(id+","+name+","+age); 250 251 //将获取的数据封装成对应的Student对象 252 Student stu = new Student(id, name, age); 253 //将一个个Student对象添加到list集合中 254 stus.add(stu); 255 } 256 257 } catch (Exception e) { 258 // TODO: handle exception 259 }finally { 260 try { 261 if(rs !=null) { 262 rs.close(); 263 } 264 } catch (SQLException e) { 265 e.printStackTrace(); 266 }finally { 267 try { 268 if(ps !=null) { 269 ps.close(); 270 } 271 } catch (SQLException e) { 272 e.printStackTrace(); 273 }finally { 274 try { 275 if(conn !=null) { 276 conn.close(); 277 } 278 } catch (SQLException e) { 279 e.printStackTrace(); 280 } 281 } 282 } 283 } 284 285 return stus; 286 } 287 288 }
7.3.10. 快速生成单元测试类
一个dao层或者service编写代码以后,需要为每一个功能都进行单元测试,一个dao中的方法很多。我们快速为这个dao层的类生成单元测试类,(dao的每一个方法都自动生成一个测试方法)
|
|
|
|
7.4. 代码初步重构
上述的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.4.1. JdbcUtil工具类
1 package cn.sxt.jdbc.util; 2 3 import java.io.InputStream; 4 import java.sql.Connection; 5 import java.sql.DriverManager; 6 import java.sql.PreparedStatement; 7 import java.sql.ResultSet; 8 import java.sql.SQLException; 9 import java.util.Properties; 10 11 12 public class JdbcUtil { 13 14 // alt+shif+a 多行修改,修改以后还原 alt+shif+a 15 16 /*private static String driverClassName = "com.mysql.jdbc.Driver"; 17 private static String url = "jdbc:mysql://localhost:3306/jdbcdemo"; 18 private static String username = "root"; 19 private static String password = "root";*/ 20 21 private static Properties p = new Properties(); 22 23 static { 24 try { 25 //1.获取类加载器 26 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 27 //2,使用类加载器获取项目 类路径下面的文件 28 InputStream inputStream = classLoader.getResourceAsStream("db.properties"); 29 30 //3.使用Priperties加载配置文件对应的输入流 31 p.load(inputStream); 32 33 Class.forName(p.getProperty("driverClassName")); 34 } catch (Exception e) { 35 e.printStackTrace(); 36 } 37 } 38 39 public static Connection getConnection() { 40 try { 41 42 return DriverManager.getConnection(p.getProperty("url"), p.getProperty("username"), p.getProperty("password")); 43 } catch (Exception e) { 44 e.printStackTrace(); 45 throw new RuntimeException("亲,连接数据库失败", e); 46 } 47 } 48 49 public static void close(Connection conn,PreparedStatement ps,ResultSet rs) { 50 try { 51 if(rs !=null) { 52 rs.close(); 53 } 54 } catch (SQLException e) { 55 e.printStackTrace(); 56 }finally { 57 try { 58 if(ps !=null) { 59 ps.close(); 60 } 61 } catch (SQLException e) { 62 e.printStackTrace(); 63 }finally { 64 try { 65 if(conn !=null) { 66 conn.close(); 67 } 68 } catch (SQLException e) { 69 e.printStackTrace(); 70 } 71 } 72 } 73 } 74 }
7.4.2. 使用工具类以后的DAO实现类效果
1 package cn.sxt.jdbc.dao.impl; 2 3 import java.sql.Connection; 4 import java.sql.PreparedStatement; 5 import java.sql.ResultSet; 6 import java.sql.SQLException; 7 import java.util.ArrayList; 8 import java.util.List; 9 10 import cn.sxt.jdbc.dao.StudentDao; 11 import cn.sxt.jdbc.domain.Student; 12 import cn.sxt.jdbc.util.JdbcUtil; 13 14 public class StudentDaoImpl implements StudentDao { 15 16 17 18 @Override 19 public int saveStudent(Student stu) { 20 String sql = "insert into t_student(name,age) values (?,?)"; 21 22 Connection conn = null; 23 PreparedStatement ps = null; 24 try { 25 conn = JdbcUtil.getConnection(); 26 27 // 3.创建预编译语句对象 28 ps = conn.prepareStatement(sql); 29 //3.1设置占位符参数 30 ps.setString(1, stu.getName()); 31 ps.setInt(2, stu.getAge()); 32 33 // 4.执行SQL语句:注意不要带SQL参数 34 return ps.executeUpdate(); 35 36 } catch (Exception e) { 37 e.printStackTrace(); 38 }finally { 39 JdbcUtil.close(conn, ps, null); 40 } 41 return 0; 42 } 43 44 @Override 45 public int deleteById(int id) { 46 47 String sql = "delete from t_student where id = ?"; 48 49 Connection conn = null; 50 PreparedStatement ps = null; 51 try { 52 53 conn = JdbcUtil.getConnection(); 54 // 3.创建预编译语句对象 55 ps = conn.prepareStatement(sql); 56 //3.1设置占位符参数 57 ps.setInt(1, id); 58 59 // 4.执行SQL语句:注意不要带SQL参数 60 return ps.executeUpdate(); 61 62 63 } catch (Exception e) { 64 e.printStackTrace(); 65 }finally { 66 JdbcUtil.close(conn, ps, null); 67 } 68 return 0; 69 } 70 71 @Override 72 public int updateStudentById(Student stu) { 73 74 String sql = "update t_student set name = ?,age = ? where id = ?"; 75 76 Connection conn = null; 77 PreparedStatement ps = null; 78 try { 79 80 conn = JdbcUtil.getConnection(); 81 // 3.创建预编译语句对象 82 ps = conn.prepareStatement(sql); 83 //3.1设置占位符参数 84 ps.setString(1, stu.getName()); 85 ps.setInt(2, stu.getAge()); 86 ps.setInt(3, stu.getId()); 87 // 4.执行SQL语句:注意不要带SQL参数 88 return ps.executeUpdate(); 89 90 91 } catch (Exception e) { 92 e.printStackTrace(); 93 }finally { 94 JdbcUtil.close(conn, ps, null); 95 } 96 return 0; 97 } 98 99 @Override 100 public Student selectById(int id) { 101 String sql = "select * from t_student where id = ?"; 102 103 Connection conn = null; 104 PreparedStatement ps = null; 105 ResultSet rs = null; 106 107 try { 108 conn = JdbcUtil.getConnection(); 109 // 3.创建语句对象 110 ps = conn.prepareStatement(sql); 111 //3.1设置占位符参数对应的值 112 ps.setInt(1, id); 113 114 // 4.执行SQL语句 115 rs = ps.executeQuery(); 116 if(rs.next()) { 117 //通过数据库数据和Java对应的数据类型获取对应的只 118 String name = rs.getString("name"); 119 int age = rs.getInt("age"); 120 //System.out.println(id+","+name+","+age); 121 //将获取的数据封装成对应的Student对象 122 Student stu = new Student(id, name, age); 123 return stu; 124 } 125 126 } catch (Exception e) { 127 // TODO: handle exception 128 }finally { 129 try { 130 if(rs !=null) { 131 rs.close(); 132 } 133 } catch (SQLException e) { 134 e.printStackTrace(); 135 }finally { 136 JdbcUtil.close(conn, ps, rs); 137 } 138 } 139 140 return null; 141 } 142 143 @Override 144 public List<Student> selectList() { 145 String sql = "select * from t_student"; 146 //创建list集合用于封装Student对象 147 List<Student> stus = new ArrayList<>(); 148 149 Connection conn = null; 150 PreparedStatement ps = null; 151 ResultSet rs = null; 152 153 try { 154 155 conn = JdbcUtil.getConnection(); 156 // 3.创建语句对象 157 ps = conn.prepareStatement(sql); 158 159 // 4.执行SQL语句 160 rs = ps.executeQuery(); 161 while(rs.next()) { 162 //通过数据库数据和Java对应的数据类型获取对应的只 163 int id = rs.getInt("id"); 164 String name = rs.getString("name"); 165 int age = rs.getInt("age"); 166 //System.out.println(id+","+name+","+age); 167 168 //将获取的数据封装成对应的Student对象 169 Student stu = new Student(id, name, age); 170 //将一个个Student对象添加到list集合中 171 stus.add(stu); 172 } 173 174 } catch (Exception e) { 175 // TODO: handle exception 176 }finally { 177 JdbcUtil.close(conn, ps, rs); 178 } 179 180 return stus; 181 } 182 183 }
7.5. 知识点补充,类加载器
在项目的 类路径(src)下面创建一个 db.properties配置文件,专门配置连接数据库的账号密码
如何使用类加载器加载配置文件
7.5.1. 配置文件
|
7.5.1.1. 配置文件创建的位置
配置文件一般都放在项目的src 源目录下面
|
7.5.2. 加载代码
1 package cn.sxt.jdbc.test; 2 3 import static org.junit.Assert.*; 4 5 import java.io.InputStream; 6 import java.util.Properties; 7 8 import org.junit.Test; 9 10 public class PropertiesTest { 11 12 @Test 13 public void testName() throws Exception { 14 15 /* 16 * ClassLoader 类加载器 17 * ClassLoader :可以从项目的类路径下面读取对应的配置文件返回一个输入流 18 * ClassLoader 在程序运行的时候JVM已经为每一个项目都创建了一个,我们开发者只需要获取即可 19 * 获取类加载器方式 20 * 1、使用当前线程 21 * ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 22 * 2、通过某一类的字节码实例也可以获取 23 * ClassLoader classLoader = PropertiesTest.class.getClassLoader(); 24 */ 25 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 26 //使用类加载器获取项目 类路径下面的文件 27 InputStream inputStream = classLoader.getResourceAsStream("db.properties"); 28 29 30 /* 31 * Properties 是Map集合下面的一个 专门用于读取配置文件的对象 32 * 可以读取当前类路径下面的 xxx.properites类型的配置文件 33 * 34 * xxx.properites的内容必须是key=value 键值对的数据 35 */ 36 37 //1.创建Properties对象 38 Properties p = new Properties(); 39 40 //2.加载配置文件 41 p.load(inputStream); 42 43 System.out.println(p); 44 45 //获取具体某一个key对应的值 46 String driverClassName = p.getProperty("driverClassName"); 47 System.out.println(driverClassName); 48 } 49 }
7.5.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. 使用连接池和不使用连接池的区别在哪里
8.5. Druid连接池的使用
8.5.1. 准备druid 连接池jar包到项目
从代码上:
不使用连接池: Conenction对象由DriverManager获取.
Connection conn = DriverManager.getConnection(url,username,password);
使用连接池:
如何创建DataSource对象,如何在DataSource中设置url,账号,密码.
Connection conn = DataSource对象.getConnection();
--------------------------------------------------------------------
使用连接池的时候:
释放资源: Connection对象.close():
是把Connection放回给连接池,而不是和数据库断开.
1 package cn.sxt.jdbc.test; 2 3 import static org.junit.Assert.*; 4 5 import java.io.InputStream; 6 import java.io.Reader; 7 import java.sql.Connection; 8 import java.util.Properties; 9 10 import javax.sql.DataSource; 11 12 import org.junit.Test; 13 14 import com.alibaba.druid.pool.DruidDataSource; 15 import com.alibaba.druid.pool.DruidDataSourceFactory; 16 import com.alibaba.druid.pool.DruidPooledConnection; 17 18 public class DataSourceTest { 19 // 直接创建连接池对象 20 @Test 21 public void testName() throws Exception { 22 // 1.创建连接池对象 23 DruidDataSource ds = new DruidDataSource(); 24 // 2.设置连接数据库的账号密码 25 ds.setDriverClassName("com.mysql.jdbc.Driver"); 26 ds.setUrl("jdbc:mysql://localhost:3306/jdbcdemo"); 27 ds.setUsername("root"); 28 ds.setPassword("root"); 29 ds.setMaxActive(10);// 最大连接数 30 // 3.获取连接对象 31 Connection conn = ds.getConnection(); 32 System.out.println(conn); 33 } 34 35 // 使用工厂对象创建连接池对象,工厂对象的好处,不需要直接设置账号密码等等,只需要将 36 // 连接数据库的账号密码等等以指定的 key的名称配置到 xxx.properties文件中即可,工厂对象底层自动读取 37 @Test 38 public void testDataSourceByFactory() throws Exception { 39 40 // 1.获取类加载器用于加载clsspath下面的 配置文件 41 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 42 // 2.读取druid.properties配置文件 43 InputStream inputStream = classLoader.getResourceAsStream("druid.properties"); 44 // 3.创建Properties对象,并读取配置文件对应的输入流 45 Properties p = new Properties(); 46 p.load(inputStream); 47 48 // 4.创建连接池对象 49 DataSource ds = DruidDataSourceFactory.createDataSource(p); 50 // 5.获取连接对象 51 Connection conn = ds.getConnection(); 52 System.out.println(conn); 53 } 54 }
|
8.5.2. db.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不支持外键,不支持事务.
InnoDB存储引擎: 支持事务,支持外键,但是查询效率略低,(金融,理财,p2p)
MyISAM存储引擎:不支持事务和外键,但是查询效率较高(新闻网站)
Oracle 不存在存储引擎,都有事务
9.2. 事务处理代码
1 public class TransactionTest { 2 @Test 3 public void testName() throws Exception { 4 Connection conn = null; 5 Statement st = null; 6 ResultSet rs = null; 7 try { 8 conn = DruidUtil.getConnection(); 9 //将事务设置为手动提交 10 conn.setAutoCommit(false); 11 12 st = conn.createStatement(); 13 // 1.检查张无忌的账号余额是否大于等于1000. 14 rs = st.executeQuery("SELECT balance FROM account WHERE name = '张无忌' AND balance >=1000"); 15 if(!rs.next()) { 16 throw new RuntimeException("亲,您的账户余额不够"); 17 } 18 // 余额>=1000:GOTO 2: 19 // 余额 <1000:提示:亲,你的余额不足. 20 // 2.在张无忌的账号余额上减少1000. 21 st.executeUpdate("UPDATE account SET balance = balance-1000 WHERE name = '张无忌'"); 22 23 System.out.println(1/0); 24 25 // 3.在赵敏的账户余额尚增加1000. 26 st.executeUpdate("UPDATE account SET balance = balance+1000 WHERE name = '赵敏'"); 27 28 //提交事务 29 conn.commit(); 30 31 32 } catch (Exception e) { 33 e.printStackTrace(); 34 //回滚事务 35 conn.rollback(); 36 37 }finally { 38 DruidUtil.close(conn, st, rs); 39 } 40 41 } 42 }