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 查询具体操作
结果集的列的位置
1. 使用 rs.next() 偏移光标,循环指定具体的某一行
获取数据的具体方法
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?
优势
1. SQL语句结构清晰,参数的设置和SQL语句分离
2. 性能更高
3. 防止SQL注入
Statement: 表示静态SQL语句对象.
PreparedStatement:Statement的子接口,表示预编译SQL语句对象. 通过占位符(?)来拼SQL.
6.1 创建PreparedStatement
创建语句对象 Statment
6.2 执行SQL语句的方法
6.2.1 Statement
在执行SQL语句的时候会带上SQL语句
6.2.2 PreparedStatemeny
在执行SQL语句的方法中不需要设置SQL语句
6.3 设置站位参数的值
void setXxx(int parameterIndex,Xxx value):用于设置占位符参数,
parameterIndex:第几个问号. 注意:从1开始.
value:设置的真实值.
Xxx:表示数据类型.String/int/long/Double/Date
6.4 代码
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开发的分层设计-三层架构
实际开发中,JavaWeb开发代码一般分为三层,分层结构是JavaWeb开发中的一种设计思想,这样会让我们开发层次分明,每一层只要完成对应的功能即可,使得项目便于开发和维护
1 . Web层/表现层 : 主要接受前台浏览器用户的参数,给浏览器响应数据等等
2. Service层/业务成/服务层:主要处理业务功能,日志,权限,事物,等等
3. 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的实现类代码
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.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 工具类
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.4.2 使用工具类以后的DAO实现类效果
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.5 知识点补充,类加载器
在项目的 类路径(src)下面创建一个 db.properties配置文件,专门配置连接数据库的账号密码
如何使用类加载器加载配置文件
7.5.1 配置文件
7.5.1.1 配置文件创建的位置
配置文件一般都放在项目的src 源目录下面
7.5.2 加载代码
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.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 使用连接池和不使用连接池的区别在哪里
从代码上:
不使用连接池: 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包到项目
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 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. 以后,凡是发现自己编写的代码是正确的,测试也通过,但是就是数据库表中的数据不变----->事务没提交的问题.
4.MySQL中,InnoDB支持外键.支持事务,MyISAM不支持外键,不支持事务.
InnoDB存储引擎: 支持事务,支持外键,但是查询效率略低,(金融,理财,p2p)
MyISAM存储引擎:不支持事务和外键,但是查询效率较高(新闻网站)
Oracle 不存在存储引擎,都有事务
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);
}
}
}