JDBC!从精通到入土
1、JDBC基础
JDBC:Java Database Conectivity
Java操作数据库的一种技术,在Java中定义了一套操作数据库的标准!
1、JDBC的基本规范
2、预处理语句
3、DAO模式
4、数据库连接池
5、DBUtils的使用
公司域名.项目名称.模块.结构名
com.itluma.myshop.user.dao
1.1 JDBC介绍
JDBC是java中规范操作数据库的一套技术,这套技术中就定义了大量的接口
Connection
:连接接口- DriverManager:驱动管理器
Statement
:声明接口ResultSet
:结果集接口PreparedStatement
:预处理声明接口
- 规范:JDBC,java操作数据库的一套标准
- 驱动:每个数据库按照规范要求的具体实现
1.2 JDBC入门案例
JDBC的实现过程
1、加载驱动
2、建立连接对象
3、创建声明语句
4、发送SQL语句
5、分析结果集(只有查询才会需要)
6、关闭
在开始JDBC之前,先准备好驱动包
mysql-connector-java-5.1.37-bin.jar,并还需要将该驱动包添加到项目的类路径中
将jar拷贝到该lib目录中,选中jar包,右键,Add as Library
class MyTest{
public MyTest(){
System.out.println("3 MyTest构造函数");
}
static{
//在类加载的时候执行,而且每个类在它的生命过程中只会加载一次
System.out.println("1 static代码块!");
}
{
System.out.println("2 普通代码块!");
}
}
1 static代码块!
2 普通代码块!
3 MyTest构造函数
2 普通代码块!
3 MyTest构造函数
public class JdbcTest {
public static void main(String[] args) {
try {
//1、加载驱动
Class.forName("com.mysql.jdbc.Driver");
//2、建立连接对象
//建立连接的url地址格式:jdbc:mysql://数据库地址:3306/数据库名
String url = "jdbc:mysql://localhost:3306/companydb";
Connection conn = DriverManager.getConnection(url, "root", "123456");
//3、创建声明语句
Statement statement = conn.createStatement();
//4、发送SQL语句
String sql = "select * from t_countries";
ResultSet rs = statement.executeQuery(sql);
System.out.println(rs);
// 5、分析结果集(只有查询才会需要)
while (rs.next())//true:当前行存在数据, false:当前行没有数据了
{
//显示当前行中的每一列数据
//rs.getXXXX("列名"):获取当前列对应的数据,XXXX:当前列的数据类型
System.out.println(rs.getString("country_id") + " " + rs.getString("country_name"));
}
//6、关闭
rs.close();
statement.close();
conn.close();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
1.3 API说明
- 加载驱动:创建一个驱动对象, new xxx()
Class.forName(“com.mysql.jdbc.Driver”);//通过反射创建一个Driver对象,在JDK1.8中可以省略
- 获取连接对象:建立数据库和Java程序之间的连接
- url:连接的格式:
- jdbc:mysql://localhost:3306/companydb
- 协议:子协议://数据库服务器地址:端口/数据库名
- username、password:访问数据库的用户名和密码
DriverManager.getConnection(url, username, password);
连接对象:java.sql.Connection
Statement createStatement() throws SQLException;//获取声明对象,主要用来操作SQL语句
声明对象:java.sql.Statement,操作SQL语句,并返回相应结果
Statement statement = conn.createStatement();
常用方法:
- ResultSet executeQuery(String sql):执行select语句
- int executeUpdate(String sql):执行insert,update,delete,返回的是影响的行数
- boolean execute(String sql):可以执行各种sql语句,如果执行的是select则返回true,否则返回false
- 结果集:java.sql.ResultSet(执行insert,update,delete不要处理结果集对象了)
结果集其实就是SELECT语句执行的结果,可以理解成一个二维表,就分行和列。boolean next()指向某行的数据,如果是true则表示当前行存在数据,否则表示结束!getXXX(字段名),获取当前行指定列的数据,xxx是表示对应的字段类型
rs.next()://指向当前行,如果返回true则表示当前行存在 rs.getInt("字段");//rs.getInt(1);1表示当前字段在表中的位置
释放资源
1.4 JDBC工具类
我们发现,只要进行JDBC操作,都需要先
加载驱动
然后建立连接对象
,执行完成之前需要关闭连接
对象。可以考虑设计一个通用代码出来。
创建新项目
定义工具类
package com.itluma.utils;
import java.sql.*;
public class JdbcUtils {
//1.加载驱动
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//2.建立连接
public static Connection getConnection() {
try {
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/companydb", "root", "123456");
return conn;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
//3.实现资源
public static void closeAll(Connection conn, Statement stmt, ResultSet rs) {
try {
if (rs != null)
rs.close();
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
driverClassName、url、username、password
这几个参数应该可配置的,那么不要直接写在java文件中,然后在java程序中去读取配置文件中的参数值。
可以使用properties文件保存数据,该文件存储数据的特点是key=value
在src目录下新建db.properties文件
driverName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/companydb
username=root
password=123456
package com.itluma.utils;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class JdbcUtils {
private static final Properties PROPERTIES = new Properties();
//1.加载驱动
static {
//
InputStream is = JdbcUtils.class.getResourceAsStream("/db.properties");
try {
PROPERTIES.load(is);
Class.forName(PROPERTIES.getProperty("driverName"));
} catch (ClassNotFoundException | IOException e) {
e.printStackTrace();
}
}
//2.建立连接
public static Connection getConnection() {
try {
Connection conn = DriverManager.getConnection(PROPERTIES.getProperty("url"), PROPERTIES.getProperty("username"), PROPERTIES.getProperty("password"));
return conn;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
//3.实现资源
public static void closeAll(Connection conn, Statement stmt, ResultSet rs) {
try {
if (rs != null)
rs.close();
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
Properties类是专门用来获取Properties文件中的数据的
public class GetDataFromPropertiesFile {
public static void main(String[] args) {
//将Properties文件中的值读取出来
Properties p = new Properties();
//InputStream is = new FileInputStream("C;//xxxx/src/db.properties");
//1、构建流对象
InputStream is = GetDataFromPropertiesFile.class.getResourceAsStream("/db.properties");
try {
//2、加载properties文件
p.load(is);
//3、获取propeties中driverName指定的value
System.out.println(p.getProperty("driverName"));
System.out.println(p.getProperty("url"));
System.out.println(p.getProperty("username"));
System.out.println(p.getProperty("password"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
1.5 JDBC增删改查操作
1.5.1 增加
public class InsertJdbc {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try {
conn = JdbcUtils.getConnection();
stmt = conn.createStatement();
String sql = "insert into t_countries values('AA','Axxxaaxxx')";
int rows = stmt.executeUpdate(sql);
if (rows > 0) {
System.out.println("添加成功!");
} else {
System.out.println("添加失败!");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.closeAll(conn, stmt, null);
}
}
}
1.5.2 修改
package com.itluma.dbtest;
import com.itluma.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
public class UpdateJdbc {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try {
conn = JdbcUtils.getConnection();
stmt = conn.createStatement();
String sql = "update t_countries set country_name='American' where country_id='AA'";
int rows = stmt.executeUpdate(sql);
if (rows > 0) {
System.out.println("修改成功!");
} else {
System.out.println("修改失败!");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.closeAll(conn, stmt, null);
}
}
}
1.5.3 删除
public class DeleteJdbc {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try {
conn = JdbcUtils.getConnection();
stmt = conn.createStatement();
String sql = "delete from t_countries where country_id='AA'";
int rows = stmt.executeUpdate(sql);
if (rows > 0) {
System.out.println("删除成功!");
} else {
System.out.println("删除失败!");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.closeAll(conn, stmt, null);
}
}
}
1.5.4 根据id查询
public class FindByIdJdbc {
public static void main(String[] args) {
//从键盘中获取country_id值
Scanner scan = new Scanner(System.in);
System.out.print("请输入country_id:");
String countryId = scan.next();
try {
Connection conn = JdbcUtils.getConnection();
//3、创建声明语句
Statement statement = conn.createStatement();
//4、发送SQL语句
String sql = "select * from t_countries where country_id='"+countryId+"'";
System.out.println(sql);
ResultSet rs = statement.executeQuery(sql);
// 5、分析结果集(只有查询才会需要)
if (rs.next()) {
System.out.println(rs.getString("country_id") + " " + rs.getString("country_name"));
}
//6、关闭
JdbcUtils.closeAll(conn, statement, rs);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
1.6 预处理对象(PreparedStatement)
1.6.1 SQL注入问题
用户登录案例:
create table users( id int primary key auto_increment, username varchar(200), password varchar(200) ); insert into users values(null, 'admin','admin'); insert into users values(null, 'zhangsan','123456'); #实现一个用户登录 select *from users where username='输入的用户名' and password='输入的密码';
用户名和密码,是用户从输入设备输入进来,可以是任意字符组成!
usrname=‘XXXX’ or 1=1 #’
select *from users where username=‘XXXX’ or 1=1 --’ and password=‘输入的密码’;
SQL注入问题:就是输入一些特殊字符,破坏了SQL语句原有的业务功能!
public class SQLInject {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
System.out.print("请输入用户名:");
String name = scan.nextLine();
System.out.print("请输入密码:");
String password = scan.nextLine();
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
stmt = conn.createStatement();
String sql = "SELECT * FROM users WHERE username='" + name + "' AND PASSWORD='" + password + "'";
rs = stmt.executeQuery(sql);
if (rs.next()) {
System.out.println("登录成功!");
} else {
System.out.println("用户名或密码错误!");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.closeAll(conn, stmt, rs);
}
}
}
请输入用户名:fasdfasdf' or 1=1 #
请输入密码:fjasdklfasdf
登录成功!
我们发现SQL验证用户的功能失效了!!!
PreparedStatement
可以防止出现SQL注入问题!
1.6.2 PreparedStatment
预编译对象,是Statement的子 接口
- 性能高
- 会把SQL语句提前进行编译
- 而且会过滤用户输入的一些关键词
//SQL语句中的值只需要使用占位符?即可。具体值在执行这条SQL语句时提供
String sql = "SELECT * FROM users WHERE username=? AND PASSWORD=?;
使用PreparedStatement步骤:
1、创建PreparedStatement对象
PreparedStatement pstmt = conn.prepareStatement(sql);
2、设置占位符所对应的参数
setXXX(int index, Xxx xxx):设置指定位置对应的值
参数1:index实际占位符的位置,从1开始
参数2:xxx实际参数值, Xxx表示具体的类型
pstmt.setString(1, name)
3、执行SQL语句
ResultSet executeQuery():执行select语句
int executeUpdate():执行insert,update,delete语句
boolean execute():执行select返回true,否则返回false
public class SQLInject2 {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
System.out.print("请输入用户名:");
String name = scan.nextLine();
System.out.print("请输入密码:");
String password = scan.nextLine();
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
String sql = "SELECT * FROM users WHERE username=? AND PASSWORD=?";
pstmt = conn.prepareStatement(sql);
//设置占位符的内容
pstmt.setString(1, name);//给SQL语句中第一个?设置值为admin
pstmt.setString(2, password);
rs = pstmt.executeQuery();
if (rs.next()) {
System.out.println("登录成功!");
} else {
System.out.println("用户名或密码错误!");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.closeAll(conn, stmt, rs);
}
}
}
2、DAO实现
Data Access Object,将数据库的访问抽象在一起,叫DAO层
DAO其实就是定义了数据库操作的接口,提供了项目的灵活性!
采用DAO的设计思想对t_employees表进行操作!
2.1 DAO的接口
1、数据封装
对操作表的数据进行封装,当前类中字段和数据库中表的字段一一对应
public class Employee {
private Integer employeeId;
private String firstName;
private String lastName;
private String email;
private String phoneNumber;
private Date hireDate;
private String jobId;
private Integer salary;
private double comm;
private String managerId;
private String departmentId;
public Integer getEmployeeId() {
return employeeId;
}
public void setEmployeeId(Integer employeeId) {
this.employeeId = employeeId;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public Date getHireDate() {
return hireDate;
}
public void setHireDate(Date hireDate) {
this.hireDate = hireDate;
}
public String getJobId() {
return jobId;
}
public void setJobId(String jobId) {
this.jobId = jobId;
}
public Integer getSalary() {
return salary;
}
public void setSalary(Integer salary) {
this.salary = salary;
}
public double getComm() {
return comm;
}
public void setComm(double comm) {
this.comm = comm;
}
public String getManagerId() {
return managerId;
}
public void setManagerId(String managerId) {
this.managerId = managerId;
}
public String getDepartmentId() {
return departmentId;
}
public void setDepartmentId(String departmentId) {
this.departmentId = departmentId;
}
}
2、定义数据操作接口
public interface EmployeeDAO {
/**
* 增加雇员对象
* @param employee 需要添加的具体雇员信息
* @return 增加成功后的雇员信息
*/
Employee insert(Employee employee);
/**
* 修改雇员信息
* @param employee 需要修改的雇员对象
* @return 修改成功后的雇员对象
*/
Employee update(Employee employee);
/**
* 根据雇员Id删除雇员数据
* @param employeeId 雇员Id
* @return 删除成功返回true,否则返回false
*/
boolean delete(int employeeId);
/**
* 根据雇员Id查询雇员对象
* @param employeeId 雇员Id
* @return 返回具体雇员对象
*/
Employee findById(int employeeId);
/**
* 返回所有雇员信息
* @return
*/
List<Employee> list();
}
2.2 接口的具体实现
2.2.1 实现删除方法
@Override
public boolean delete(int employeeId) {
Connection conn = null;
PreparedStatement pstmt = null;
String sql = "delete from t_employees where employee_id=?";
try {
conn = JdbcUtils.getConnection();
pstmt = conn.prepareStatement(sql);
//设置占位符对应的值
pstmt.setInt(1, employeeId);
//执行
return pstmt.executeUpdate() > 0;
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.closeAll(conn, pstmt, null);
}
return false;
}
2.2.2 修改方法
@Override
public Employee update(Employee employee) {
Connection conn = null;
PreparedStatement pstmt = null;
String sql = "update t_employees set FIRST_NAME=?,LAST_NAME=?,EMAIL=?,PHONE_NUMBER=?,HIRE_DATE=?,JOB_ID=?,SALARY=?,COMM=?,MANAGER_ID=?,DEPARTMENT_ID=? where EMPLOYEE_ID=?";
try {
conn = JdbcUtils.getConnection();
pstmt = conn.prepareStatement(sql);
//设置占位符对应的值
pstmt.setString(1, employee.getFirstName());
pstmt.setString(2, employee.getLastName());
pstmt.setString(3, employee.getEmail());
pstmt.setString(4, employee.getPhoneNumber());
pstmt.setDate(5, employee.getHireDae());
pstmt.setString(6, employee.getJobId());
pstmt.setInt(7, employee.getSalary());
pstmt.setDouble(8, employee.getComm());
pstmt.setString(9, employee.getManagerId());
pstmt.setString(10, employee.getDepartmentId());
pstmt.setInt(11, employee.getEmployeeId());
//执行
if (pstmt.executeUpdate() > 0) {
return employee;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.closeAll(conn, pstmt, null);
}
return null;
}
2.2.3 查询
@Override
public Employee findById(int employeeId) {
Connection conn = null;
PreparedStatement pstmt = null;
String sql = "select * from t_employees where employee_id=?";
try {
conn = JdbcUtils.getConnection();
pstmt = conn.prepareStatement(sql);
//设置占位符对应的值
pstmt.setInt(1, employeeId);
//执行
ResultSet rs = pstmt.executeQuery();
if(rs.next()){
//将第一行中每一列的数据拿出来,封装成一个Employee对象
Employee e = new Employee();
e.setEmployeeId(rs.getInt(1));
e.setFirstName(rs.getString(2));
e.setLastName(rs.getString(3));
e.setEmail(rs.getString(4));
e.setPhoneNumber(rs.getString(5));
e.setHireDate(rs.getDate(6));
e.setJobId(rs.getString(7));
e.setSalary(rs.getInt(8));
e.setComm(rs.getDouble(9));
e.setManagerId(rs.getString(10));
e.setDepartmentId(rs.getString(11));
return e;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.closeAll(conn, pstmt, null);
}
return null;
}
@Override
public List<Employee> list() {
Connection conn = null;
PreparedStatement pstmt = null;
String sql = "select * from t_employees";
try {
conn = JdbcUtils.getConnection();
pstmt = conn.prepareStatement(sql);
//执行
ResultSet rs = pstmt.executeQuery();
List<Employee> list = new ArrayList<>();
while(rs.next()){
//将第一行中每一列的数据拿出来,封装成一个Employee对象
Employee e = new Employee();
e.setEmployeeId(rs.getInt(1));
e.setFirstName(rs.getString(2));
e.setLastName(rs.getString(3));
e.setEmail(rs.getString(4));
e.setPhoneNumber(rs.getString(5));
e.setHireDate(rs.getDate(6));
e.setJobId(rs.getString(7));
e.setSalary(rs.getInt(8));
e.setComm(rs.getDouble(9));
e.setManagerId(rs.getString(10));
e.setDepartmentId(rs.getString(11));
list.add(e);
}
return list;
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.closeAll(conn, pstmt, null);
}
return null;
}
2.2.4 添加
select LAST_INSERT_ID():在同一个连接中获取上一条insert语句自增的id
@Override
public Employee insert(Employee employee) {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs =null;
String sql = "insert into t_employees values (null,?,?,?,?,?,?,?,?,?,?)";
try {
conn = JdbcUtils.getConnection();
pstmt = conn.prepareStatement(sql);
//设置占位符对应的值
pstmt.setString(1, employee.getFirstName());
pstmt.setString(2, employee.getLastName());
pstmt.setString(3, employee.getEmail());
pstmt.setString(4, employee.getPhoneNumber());
pstmt.setDate(5, employee.getHireDate());
pstmt.setString(6, employee.getJobId());
pstmt.setInt(7, employee.getSalary());
pstmt.setDouble(8, employee.getComm());
pstmt.setString(9, employee.getManagerId());
pstmt.setString(10, employee.getDepartmentId());
//执行
if (pstmt.executeUpdate() > 0) {
//获取添加之后数据自增的id,并封装到当前employee对象中
String getGenId = "select last_insert_id()";
pstmt = conn.prepareStatement(getGenId);
rs = pstmt.executeQuery();//执行查询语句
if(rs.next()) {//获取第一行第一列值
employee.setEmployeeId(rs.getInt(1));
}
return employee;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.closeAll(conn, pstmt, rs);
}
return null;
}
作业:实现用户的注册和登录。
1、先提示用户进行注册,注册成功后要求进行登录
2、登录成功后显示上一次登录的时间
3、实现JDBC的过程要求采用DAO规范
3、JDBC连接"池"
3.1 连接池的原理
JDBC的基本步骤
1.创建连接
2.执行SQL语句
3.关闭连接
FileInputStream BufferedInputStream
连接池:存放连接对象的集合
解决了连接过程中性能的消耗,提高了程序执行的效率
在Java中提供了一个连接池标准:javax.sql.DataSource,各个连接池厂商需要自己去实现该接口,这样就很方便的实现不同连接池之间的切换!
常见连接池:DBCP、C3P0、Druid、HikariCP
3.2 常见的连接池
3.2.1 DBCP连接池对象(了解)
DBCP:Apache推出的,在Commons项目下
使用步骤:
1.添加jar包, commons-dbcp-1.4.jar,commons-pool-1.5.6.jar
2.设置配置文件
- 文件名:*.properties
- 位置:任意(src目录下classpath)
- 内容:不能写中文
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mydb
username=root
password=123456
#初始连接个数
initialSize=10
#最大活跃个数
maxTotal=10
#最大空闲的个数
maxIdle=5
#最小空闲个数
minIdle=3
#最大等待时间(毫秒)
maxWaitMills=1000
拷贝需要jar包
设计使用连接池的JdbcUtils工具类
package com.itluma.pool.utils;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class JdbcUtils {
private static final Properties PROPERTIES = new Properties();
//声明一个连接池对象
private static DataSource ds = null;
//1.加载驱动
static {
//
InputStream is = JdbcUtils.class.getResourceAsStream("/db.properties");
try {
PROPERTIES.load(is);
//构造具体的连接池对象:ds
ds = BasicDataSourceFactory.createDataSource(PROPERTIES);
} catch (Exception e) {
e.printStackTrace();
}
}
//2.建立连接
public static Connection getConnection() {
try {
return ds.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
//3.实现资源
public static void closeAll(Connection conn, Statement stmt, ResultSet rs) {
try {
if (rs != null)
rs.close();
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
3.2.2 C3P0连接池(了解)
C3P0免费开源的连接池对象!目前使用该连接池的项目有:Hibernate,Spring等这些都在使用该连接池技术。使用第三方工具导入jar包,而且还需要添加一个配置文件
c3p0-config.xml
使用步骤:
1.添加jarc3p0-0.9.5.2.jar
mchange-commons-java-0.2.12.jar
2.编写配置文件
c3p0-config.xml,可以放在src目录中(注意:文件名不能写错了)
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config>
<!-- 配置数据库用户名 -->
<property name="user">root</property>
<!-- 配置数据库密码 -->
<property name="password">123456</property>
<!-- 配置数据库链接地址 -->
<property name="jdbcUrl">jdbc:mysql://localhost:3306/companydb</property>
<!-- 配置数据库驱动 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<!-- 数据库连接池一次性向数据库要多少个连接对象 -->
<property name="acquireIncrement">20</property>
<!-- 初始化连接数 -->
<property name="initialPoolSize">10</property>
<!-- 最小连接数 -->
<property name="minPoolSize">5</property>
<!--连接池中保留的最大连接数。Default: 15 -->
<property name="maxPoolSize">30</property>
<!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements 属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default:0 -->
<property name="maxStatements">0</property>
<!--maxStatementsPerConnection定义了连接池内单个连接所拥有的最大缓存statements数。Default: 0 -->
<property name="maxStatementsPerConnection">0</property>
<!--c3p0是异步操作的,缓慢的JDBC操作通过帮助进程完成。扩展这些操作可以有效的提升性能 通过多线程实现多个操作同时被执行。Default:3 -->
<property name="numHelperThreads">3</property>
<!--用户修改系统配置参数执行前最多等待300秒。Default: 300 -->
<property name="propertyCycle">3</property>
<!-- 获取连接超时设置 默认是一直等待单位毫秒 -->
<property name="checkoutTimeout">1000</property>
<!--每多少秒检查所有连接池中的空闲连接。Default: 0 -->
<property name="idleConnectionTestPeriod">3</property>
<!--最大空闲时间,多少秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
<property name="maxIdleTime">10</property>
<!--配置连接的生存时间,超过这个时间的连接将由连接池自动断开丢弃掉。当然正在使用的连接不会马上断开,而是等待它close再断开。配置为0的时候则不会对连接的生存时间进行限制。 -->
<property name="maxIdleTimeExcessConnections">5</property>
<!--两次连接中间隔时间,单位毫秒。Default: 1000 -->
<property name="acquireRetryDelay">1000</property>
<!--c3p0将建一张名为Test的空表,并使用其自带的查询语句进行测试。如果定义了这个参数那么属性preferredTestQuery将被忽略。你不能在这张Test表上进行任何操作,它将只供c3p0测试使用。Default: null -->
<property name="automaticTestTable">Test</property>
<!-- 获取connnection时测试是否有效 -->
<property name="testConnectionOnCheckin">true</property>
</default-config>
</c3p0-config>
编写JdbcUtils工具类
package com.itluma.pool.utils;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class JdbcUtils2 {
//声明一个连接池对象
private static DataSource ds = new ComboPooledDataSource();
//2.建立连接
public static Connection getConnection() {
try {
return ds.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
//3.实现资源
public static void closeAll(Connection conn, Statement stmt, ResultSet rs) {
try {
if (rs != null)
rs.close();
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
3.2.3 Druid连接池(掌握使用)
Druid相当于DBCP和C3p0而言性能非常不错。阿里的
使用步骤:
1、添加jar
druid-1.1.5.jar
2、创建配置文件
- 文件名:*.properties
- 名称任意,建议存放在src目录下
driverClassName=com.mysql.jdbc.Driver
username=root
password=123456
url=jdbc:mysql://localhost:3306/companydb
#初始连接池大小
initialSize=10
#最大连接个数
maxActive=10
#最大等待时间
maxWait=1000
创建JdbcUtils工具类
package com.itluma.pool.utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
public class DruidUtils {
private static final Properties PROPERTIES = new Properties();
//声明一个连接池对象
private static DataSource ds = null;
//1.加载驱动
static {
//
InputStream is = DruidUtils.class.getResourceAsStream("/druid.properties");
try {
PROPERTIES.load(is);
//构造具体的连接池对象:ds
ds = DruidDataSourceFactory.createDataSource(PROPERTIES);
} catch (Exception e) {
e.printStackTrace();
}
}
//2.建立连接
public static Connection getConnection() {
try {
return ds.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
//3.实现资源
public static void closeAll(Connection conn, Statement stmt, ResultSet rs) {
try {
if (rs != null)
rs.close();
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
4、DbUtils工具类的使用
DBUtils:简化JDBC代码开发的一个工具类,是apache旗下commons项目中的
DbUtils就是JDBC的一个简化开发工具包。需要使用到技术:连接池(获取连接)、SQL语句
4.1 JavaBean组件
JavaBean其实就是一个类,在开发中经常会使用一些数据封装。具有如下特性:
1、需要实现一个接口:java.io.Serializable,通常省略
2、提供私有字段:private 类型 字段名
3、给私有字段提供getter、setter
4、提供无参的构造方法
public class Employee {
private Integer employeeId;//字段(field),成员变量
private String firstName;
private String lastName;
private String email;
private String phoneNumber;
private Date hireDate;
private String jobId;
private Integer salary;
private Double comm;
private String managerId;
private String departmentId;
//属性(property) employeeId
public Integer getEmployeeId() {
return employeeId;
}
public void setEmployeeId(Integer employeeId) {
this.employeeId = employeeId;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public Date getHireDate() {
return hireDate;
}
public void setHireDate(Date hireDate) {
this.hireDate = hireDate;
}
public String getJobId() {
return jobId;
}
public void setJobId(String jobId) {
this.jobId = jobId;
}
public Integer getSalary() {
return salary;
}
public void setSalary(Integer salary) {
this.salary = salary;
}
public Double getComm() {
return comm;
}
public void setComm(Double comm) {
this.comm = comm;
}
public String getManagerId() {
return managerId;
}
public void setManagerId(String managerId) {
this.managerId = managerId;
}
public String getDepartmentId() {
return departmentId;
}
public void setDepartmentId(String departmentId) {
this.departmentId = departmentId;
}
@Override
public String toString() {
return "Employee{" +
"employeeId=" + employeeId +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", email='" + email + '\'' +
", phoneNumber='" + phoneNumber + '\'' +
", hireDate=" + hireDate +
", jobId='" + jobId + '\'' +
", salary=" + salary +
", comm=" + comm +
", managerId='" + managerId + '\'' +
", departmentId='" + departmentId + '\'' +
'}';
}
}
一般字段都设计成封装类型,包括基础类型
int a = (Integer)null;//出现NullPointerException
Integer a = (Integer)null;//没错
4.2 DbUtils完成CRUD
DbUtils中封装了数据库操作的相关方法,小巧灵活!
主要有三个核心功能:
- QueryRunner提供大量关于SQL操作的API
- ResultSetHanlder接口,用来对select操作之后的数据进行封装。
- DbUtils类,提供关于资源的建立和关系以及事务相关操作的方法
QueryRunner核心类
- QueryRunner(DataSource ds):提供数据源,DbUtils底层自动维护连接对象
- update(String sql , Object … params):执行更新操作,insert,update,delete
- query(String sql , ResultSetHanlder rsh, Object… params):执行查询select
ResultSetHandler结果集的处理类
类名 | 说明 |
---|---|
BeanHandler | 将一条记录转换成javabean对象 |
BeanListHandler | 将结果集中的每条记录转换成javabean对象,并且存入到List集合中 |
ScalarHandler |
使用的基本步骤:
1、导入jar包:commons-dbutils-1.4.jar
2、创建连接池对象
3、DbUtils工具类的具体使用了
4.2.1 实现添加操作
在项目中创建lib目录,并将对应jar拷贝进去
需要将这个三个jar,添加到library中
提供连接池的配置文件:src/durid.properties文件
driverClassName=com.mysql.jdbc.Driver
username=root
password=123456
url=jdbc:mysql://localhost:3306/companydb
#初始连接池大小
initialSize=10
#最大连接个数
maxActive=10
#最大等待时间
maxWait=1000
提供一个连接池的工具类
package com.itluma.utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
public class DruidUtils {
private static final Properties PROPERTIES = new Properties();
private static DataSource dataSource;
static {
InputStream is = DruidUtils.class.getResourceAsStream("/druid.properties");
try {
PROPERTIES.load(is);
dataSource = DruidDataSourceFactory.createDataSource(PROPERTIES);
} catch (Exception e) {
e.printStackTrace();
}
}
public static DataSource getDataSource() {
return dataSource;
}
public static Connection getConnection() {
try {
return dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
//3.实现资源
public static void closeAll(Connection conn, Statement stmt, ResultSet rs) {
try {
if (rs != null)
rs.close();
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
添加操作
package com.itluma.dao.impl;
import com.itluma.dao.EmployeeDAO;
import com.itluma.domain.Employee;
import com.itluma.utils.DruidUtils;
import org.apache.commons.dbutils.QueryRunner;
import java.sql.SQLException;
public class EmployeeDAOImpl implements EmployeeDAO {
@Override
public Employee insert(Employee employee) {
try {
//创建QuryRunner对象,主要用来执行SQL操作
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
//执行SQL操作
String sql = "insert into t_employees values (null,?,?,?,?,?,?,?,?,?,?)";
//参数
Object[] params = {employee.getFirstName(), employee.getLastName(), employee.getEmail(), employee.getPhoneNumber(), employee.getHireDate(), employee.getJobId(), employee.getSalary(), employee.getComm(), employee.getManagerId(), employee.getDepartmentId()};
int rows = qr.update(sql, params);
if (rows > 0) {
//获取当前行的主键自增数据
//TODO 后续补充
//select last_insert_id();
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
}
4.2.2 实现修改操作
@Override
public void update(Employee employee) {
QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
String sql = "update t_employees set first_name=?,last_name=? where employee_id=?";
Object[] obj = {employee.getFirstName(), employee.getLastName(), employee.getEmployeeId()};
try {
//执行
queryRunner.update(sql, obj);
} catch (SQLException e) {
e.printStackTrace();
}
}
4.2.3 实现删除操作
@Override
public void delete(Integer employeeId) {
String sql = "delete from t_employees where employee_id=?";
try {
//执行
int rows = queryRunner.update(sql, employeeId);
System.out.println(rows);
} catch (SQLException e) {
e.printStackTrace();
}
}
4.2.4 实现查询(难点)
1、查询单个对象
@Override
public Employee findById(Integer employeeId) {
String sql = "select * from t_employees where employee_id=?";
try {
Employee e = queryRunner.query(sql, new MyResultSetHandler(), employeeId);
return e;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
创建了一个类实现ResultSetHandler接口:主要作用其实就是用来将ResultSet转成我们需要的具体对象
class MyResultSetHandler implements ResultSetHandler<Employee> {
@Override
public Employee handle(ResultSet rs) throws SQLException {
if (rs.next()) {
//将ResultSet对象转换成我们需要的具体对象
Employee e = new Employee();
e.setEmployeeId(rs.getInt(1));
e.setFirstName(rs.getString(2));
e.setLastName(rs.getString(3));
e.setEmail(rs.getString(4));
e.setPhoneNumber(rs.getString(5));
e.setHireDate(rs.getDate(6));
e.setJobId(rs.getString(7));
e.setSalary(rs.getInt(8));
e.setComm(rs.getDouble(9));
e.setManagerId(rs.getString(10));
e.setDepartmentId(rs.getString(11));
return e;
}
return null;
}
}
其实在DbUtils中已经提供了一个BeanHandler类,可以之间将ResultSet转成我们需要类型,这样就不要自己去定类
BeanHanlder<类型>(类型字节码)
@Override
public Employee findById(Integer employeeId) {
String sql = "select * from t_employees where employee_id=?";
try {
//Employee e = queryRunner.query(sql, new MyResultSetHandler(), employeeId);
Employee e = queryRunner.query(sql, new BeanHandler<Employee>(Employee.class), employeeId);
return e;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
但是存在部分数据丢失:实体字段名和数据库中的对应的字段名不一致的丢失了值
employe_id employeId
first_name firstName;
//定义好实体字段和数据库中字段之间的映射关系
Map<String,String> map = new HashMap<>();
map.put("EMPLOYEE_ID", "employeeId");
map.put("FIRST_NAME", "firstName");
map.put("LAST_NAME","lastName");
Employee e = queryRunner.query(sql, new BeanHandler<Employee>(Employee.class, new BasicRowProcessor(new BeanProcessor(map))), employeeId);
在构造BeanHandler对象是,提供第二个参数new BasicRowProcessor(new BeanProcessor(map)),建立数据库中表字段名和封装成对象的实体字段关系
2、查询多个对象
@Override
public List<Employee> findAll() {
String sql = "select * from t_employees";
try {
List<Employee> list = queryRunner.query(sql, new BeanListHandler<Employee>(Employee.class));
return list;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
3、查询记录个数
@Override
public Long getTotal() {
//该SQL语句只会返回一个值
String sql = "select count(*) from t_employees";
try {
//ScalarHandler:获取结果集中第一行第一列的值
Long res = queryRunner.query(sql, new ScalarHandler<Long>());
return res;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
今天的作业:
将昨天的作业用DbUtils进行重构
5、三层架构
DAO:数据访问层,实现数据库的CRUD操作
Service:实现业务的过程,用户登录,用户注册,
View:显示层,将业务处理完的数据如何展示
"高内聚、低耦合"
分三层:在项目开发中应用分层思想解决问题
吃饭-》【服务员-》厨师-》采购】分工
玩游戏-》【打开应用界面-》业务处理-》操作数据库】分层
显示层:UI(View),数据展示,界面
业务层:Service(Business),数据的业务处理
数据层:DAO(Data Access Object),操作数据库
User Interface
Business Logic Layer
Data Acess Layer
//创建java项目包的基本规范
com.javac.myshop.cart.view //存放显示层的代码
com.javac.myshop.cart.service //存放业务的接口代码
com.javac.myshop.cart.service.impl//存放业务的接口实现
com.javac.myshop.cart.dao //存放数据访问层的接口
com.javac.myshop.cart.dao.impl //存放数据访问层的接口实现
com.javac.myshop.cart.domain //存放数据的封装pojo/vo/bo/domain/entity/dto
com.javac.myshop.cart.utils
6、JDBC事务的支持
事务:从逻辑上来说是一个整体,各个操作单元都成功整个业务才算成功,否则算失败!
6.1 MySQL中事务
sql语句 | 说明 |
---|---|
start transaction | 开启事务 |
rollback | 回滚事务 |
commit | 提交事务 |
转账业务如果应用事务来解决的话
try{
开启事务
update account set balance=balance-500 where name="张三";
update account set balance=balance+500 where name="李四";
提交事务
}catch(Exception e){
回滚事务
}
6.2 JDBC中事务的支持
JDBC中Connection对象提供如下三个方法:
- setAutoCommit(boolean flag):false表示取消自动提交
- commit():提交SQL语句
- rollback():撤销SQL语句的执行
Connection conn=null;
try{
conn = DruidUtils.getConnection();
conn.setAutoCommit(false);
QueryRunner qr = new QueryRuner();
String sql1 = "update account set balance=balance-500 where name='张三'";
qr.update(conn, sql1);
String sql2 = "update account set balance=balance+500 where name='李四'";
qr.update(conn, sql2);
conn.commit();
}catch(Exception e){
conn.rollback();
}
注意:多个SQL语句在一个事务中执行,必须在同一个Connection对象中!
6.3 采用三层结构模拟转账过程
数据访问层
业务处理层
显示层
6.3.1 数据层的实现
1.实现数据层的SQL操作过程
package com.itluma.account.dao;
import java.sql.Connection;
import java.sql.SQLException;
public interface AccountDAO {
/**
* 扣款
* @param conn
* @param fromName 来源账号
* @param money 金额
*/
void fromAccount(Connection conn, String fromName, double money) throws SQLException;
/**
* 加钱
* @param conn
* @param toName 接收钱的账号
* @param money 金额
*/
void toAccount(Connection conn, String toName, double money) throws SQLException;
}
接口实现类,
有异常使劲抛
package com.itluma.account.dao.impl;
import com.itluma.account.dao.AccountDAO;
import org.apache.commons.dbutils.QueryRunner;
import java.sql.Connection;
import java.sql.SQLException;
public class AccountDAOImpl implements AccountDAO {
private QueryRunner qr = new QueryRunner();
@Override
public void fromAccount(Connection conn, String fromName, double money) throws SQLException {
qr.update(conn, "update account set balance=balance-? where name=?", money, fromName);
}
@Override
public void toAccount(Connection conn, String toName, double money) throws SQLException {
qr.update(conn, "update account set balance=balance+? where name=?", money, toName);
}
}
6.3.2 业务层的实现
实现具体的转账过程:张三要赚钱给李四了
抽象接口
package com.itluma.account.service;
public interface AccountService {
/**
* 具体转账过程
* @param fromName 来源的账号
* @param toName 接收的账号
* @param money 金额
*/
void transfer(String fromName, String toName, double money);
}
实现接口
package com.itluma.account.service.impl;
import com.itluma.account.dao.AccountDAO;
import com.itluma.account.dao.impl.AccountDAOImpl;
import com.itluma.account.service.AccountService;
import com.itluma.account.utils.DruidUtils;
import java.sql.Connection;
import java.sql.SQLException;
public class AccountServiceImpl implements AccountService {
private AccountDAO accountDAO = new AccountDAOImpl();
@Override
public void transfer(String fromName, String toName, double money) {
Connection conn = null;
try {
//开启事务
conn = DruidUtils.getConnection();
conn.setAutoCommit(false);
//1.扣钱
accountDAO.fromAccount(conn, fromName, money);
//2.加钱
accountDAO.toAccount(conn, toName, money);
//提交
conn.commit();
} catch (Exception e) {
//回滚
try {
if (conn != null){
conn.rollback();
}
} catch (SQLException e1) {
e1.printStackTrace();
}
}
}
}
6.3.3 显示层的模拟
package com.itluma.account.view;
import com.itluma.account.service.AccountService;
import com.itluma.account.service.impl.AccountServiceImpl;
public class AcountTest {
public static void main(String[] args) {
//实现转账过程
AccountService accountService = new AccountServiceImpl();
accountService.transfer("张三","李四", 500);
}
}
瑕疵:在业务中需要去获取连接对象,因为需要控制事务的开关
解决的方案是提供一个单独的类来处理事务的开关
6.3.4 优化业务的事务管理
设置一个事务的管理类
package com.itluma.account.utils;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
public class ConnectionManager {
//保存连接对象
private static Map<String, Connection> map = new HashMap<>();
//获取集合中获取一个名词叫key的连接对象
public static Connection getConnection() {
Connection conn = map.get("key");
if (conn == null) {
conn = DruidUtils.getConnection();
map.put("key", conn);
}
return conn;
}
public static void startTransaction() throws SQLException {
Connection conn = getConnection();
conn.setAutoCommit(false);
}
public static void commit() throws SQLException {
Connection conn = getConnection();
conn.commit();;
}
public static void rollback() throws SQLException {
Connection conn = getConnection();
conn.rollback();
}
}
在业务中直接调用事务管理类,来开关事务!
public class AccountServiceImpl implements AccountService {
private AccountDAO accountDAO = new AccountDAOImpl();
@Override
public void transfer(String fromName, String toName, double money) {
try {
//开启事务
ConnectionManager.startTransaction();
//1.扣钱
accountDAO.fromAccount(fromName, money);
int i = 1/0;
//2.加钱
accountDAO.toAccount(toName, money);
//提交
ConnectionManager.commit();
} catch (Exception e) {
e.printStackTrace();
//回滚
try {
ConnectionManager.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
}
}
在DAO中也对连接对象的获取做了修改
public class AccountDAOImpl implements AccountDAO {
private QueryRunner qr = new QueryRunner();
@Override
public void fromAccount(String fromName, double money) throws SQLException {
qr.update(ConnectionManager.getConnection(), "update account set balance=balance-? where name=?", money, fromName);
}
@Override
public void toAccount(String toName, double money) throws SQLException {
qr.update(ConnectionManager.getConnection(), "update account set balance=balance+? where name=?", money, toName);
}
}
多线程存在并发问题:多个线程同时转账时会存在,几个线程共用同一个Connection对象,这是非常不合理的,解决的方案是使用ThreadLocal实现Connection对象在不同线程之间隔离。
从名字我们就可以看到ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
package com.itluma.account.utils;
import java.sql.Connection;
import java.sql.SQLException;
public class ConnectionManager {
//保存连接对象
//private static Map<String, Connection> map = new HashMap<>();
private static ThreadLocal<Connection> tl = new ThreadLocal<>();
//获取集合中获取一个名词叫key的连接对象
public static Connection getConnection() {
//从ThreadLocal中获取连接对象
Connection conn = tl.get();
if(conn == null){
conn = DruidUtils.getConnection();
//向ThreadLocal中存储Connection对象
tl.set(conn);
}
return conn;
}
public static void startTransaction() throws SQLException {
Connection conn = getConnection();
conn.setAutoCommit(false);
}
public static void commit() throws SQLException {
Connection conn = getConnection();
conn.commit();
tl.remove();
}
public static void rollback() throws SQLException {
Connection conn = getConnection();
conn.rollback();
tl.remove();
}
}