2.使用数据源技术提升JDBC性能
2.1 连接池原理:
理解为存放多个连接的集合
目的:解决建立数据库连接耗费资源和时间很多的问题,提高性能。
2.2 编写标准的数据源(规范)
Java为数据库连接池提供了公共的接口:javax.sql.DataSource,各个厂商需要让自己的连接池实现这个接口。这样应用程序可以方便的切换不同厂商的连接池!
常见的连接池技术
DBCP(DataBase Connection Pool)数据库连接池,是java数据库连接池的一种,由Apache开发,通过数据库连接池,可以让程序自动管理数据库连接的释放和断开。
Druid 是Java语言中最好的数据库连接池。Druid能够提供强大的监控和扩展功能。
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。
Tomcat-JDBC是Spring Boot中自动配置优先级最高的连接池方案,它的出现是用来替代Apache早期的连接池产品——DBCP 1.x。
HikariCP同样是一个十分快速、简单、可靠的及十分轻量级的连接池,只有130KB,在GitHub上看到的是"光HikariCP"的名称,光就是说明它十分快。
性能测试
环境配置:
1:获取关闭连接性能测试
测试说明:
打开关闭次数为: 100w次
测试用例和mysql在同一台机器上面,尽量避免io的影响
mysql性能数据 (单位:ms)
测试结果:
性能表现:hikariCP>druid>tomcat-jdbc>dbcp>c3p0。
hikariCP 的性能及其优异。hikariCP号称java平台最快的数据库连接池。
hikariCP在并发较高的情况下,性能基本上没有下降。
c3p0连接池的性能很差,不建议使用该数据库连接池。
2:查询一条语句性能测试
测试说明:
查询的次数为10w次,查询的语句为 1:打开连接 2:执行 :select 1 3:关闭连接
测试用例和mysql在同一台机器上面,尽量避免io的影响
测试数据:
测试结果:
在并发比较少的情况下,每个连接池的响应时间差不多。是由于并发少,基本上没有资源竞争。
在并发较高的情况下,随着并发的升高,hikariCP响应时间基本上没有变动。
c3p0随着并发的提高,性能急剧下降。
结论 : 根据几种数据源的对比 hikari 无疑性能最优秀的,但是因为是最新技术可能存在潜在的bug,所以我们要使用目前比较稳定的阿里的druid数据源;
2.3 常用的数据源配置
2.3.1 druid数据库连接池
Druid是Java语言中最好的数据库连接池。Druid能够提供强大的监控和扩展功能。
使用步骤:
1、添加jar包
2、修改工具类:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.junit.Test;
import com.alibaba.druid.pool.DruidDataSource;
/**
* 数据库连接工具类
* @author My
*
*/
public class JdbcUtil {
// 创建数据库的连接对象
private static final String CONN_DRIVER = "com.mysql.jdbc.Driver";
private static final String CONN_URL = "jdbc:mysql://127.0.0.1:3306/jdbcdb?characterEncoding=UTF-8";
private static final String CONN_USER = "root";
private static final String CONN_PASSWORD = "root";
// 创建数据源对象
private static DruidDataSource dataSource = new DruidDataSource();
// 赋值
static{
dataSource.setDriverClassName(CONN_DRIVER);
dataSource.setUrl(CONN_URL);
dataSource.setUsername(CONN_USER);
dataSource.setPassword(CONN_PASSWORD);
}
// 编写数据库打开方法
public static Connection getConn(){
System.out.println("[系统日志]开始获取数据库连接,请稍后!");
// 创建方法的返回值
Connection conn = null;
try {
conn = dataSource.getConnection();
System.out.println("[系统日志]已获取数据库连接!" + conn);
} catch (SQLException e) {
System.err.println("[系统日志]你URL或者用户名密码写错了!");
e.printStackTrace();
}
// 返回
return conn;
}
// 编写释放资源方法
public static void closeAll(Connection conn,PreparedStatement stat,ResultSet set){
// 顺序关闭 第一个关 set 第二个关 stat 第三个关 conn
try {
if(null != set){
set.close();
System.out.println("[系统日志]对象 " + set + " 已释放!");
}
if(null != stat){
stat.close();
System.out.println("[系统日志]对象 " + stat + " 已释放!");
}
if(null != conn){
conn.close();
System.out.println("[系统日志]对象 " + conn + " 已释放!");
}
} catch (SQLException e) {
System.err.println("[系统日志]关闭个连接都能报错?嗯???");
e.printStackTrace();
}
}
}
druid数据源还有很多功能,我们后续会为大家介绍,现在我们只用最基本的获取数据库连接的方案;
3.2 案例相关知识
3.2.1 JavaBean组件 --> (实体Bean)
JavaBean就是一个类,在开发中常用于封装数据。具有如下特性
1.需要实现接口:java.io.Serializable ,通常偷懒省略了。
2.提供私有字段:private 类型字段名;
3.提供getter/setter方法:
4.提供无参构造
JavaBean是我们对OOP中封装特性的一种实现
实体类
/**
* PERSON表的对应类 Person
* @author My
*
*/
public class Person {
// 创建属性(与列一一对应的 类型对应 名称可以不对应)
private Integer id;
private String name;
private String sex;
private Integer age;
private String from;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
@Override
public String toString() {
return "Person [id=" + id + ", name=" + name + ", sex=" + sex + ", age=" + age + ", from=" + from + "]";
}
}
3.3 DBUtils完成CRUD
3.3.1概述
DBUtils是java编程中的数据库操作实用工具,小巧简单实用。
DBUtils封装了对JDBC的操作,简化了JDBC操作,可以少写代码。
Dbutils三个核心功能介绍
1.QueryRunner中提供对sql语句操作的API.
2.ResultSetHandler接口,用于定义select操作后,怎样封装结果集.
3.DbUtils类,它就是一个工具类,定义了关闭资源与事务处理的方法
3.3.2 QueryRunner核心类(用于执行SQL语句的类)
有参构造:
QueryRunner(DataSource ds) ,提供数据源(连接池),DBUtils底层自动维护连接connection
/**
* Constructor for QueryRunner that takes a <code>DataSource</code> to use.
*
* Methods that do not take a <code>Connection</code> parameter will retrieve connections from this
* <code>DataSource</code>.
*
* @param ds The <code>DataSource</code> to retrieve connections from.
*/
public QueryRunner(DataSource ds) {
super(ds);
}
主要方法:
update(String sql, Object… params) ,执行更新数据 insert update delete
query(String sql, ResultSetHandler rsh, Object… params) ,执行查询数据 select
3.3.3 ResultSetHandler结果集处理类(重要)
3.3.4 CURD案例
开发步骤:
1、创建项目,并导入jar包
2、优化JdbcUtil
因为Dbutils执行SQL语句需要传入一个DataSourc,所以我们根据这一特性将我们的JdbcUtil加以改造,得出以下优化结论
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.junit.Test;
import com.alibaba.druid.pool.DruidDataSource;
/**
* 数据库连接工具类
* @author My
*
*/
public class JdbcUtil {
// 创建数据库的连接对象
private static final String CONN_DRIVER = "com.mysql.jdbc.Driver";
private static final String CONN_URL = "jdbc:mysql://127.0.0.1:3306/jdbcdb?characterEncoding=UTF-8";
private static final String CONN_USER = "root";
private static final String CONN_PASSWORD = "root";
// 创建数据源对象
private static DruidDataSource dataSource = new DruidDataSource();
// 赋值
static{
dataSource.setDriverClassName(CONN_DRIVER);
dataSource.setUrl(CONN_URL);
dataSource.setUsername(CONN_USER);
dataSource.setPassword(CONN_PASSWORD);
}
/**
* 添加获取数据源的方法
* @return
*/
public static DruidDataSource getDataSource() {
return dataSource;
}
}
3、完成各功能并测试
3.3.4.1 创建SQL执行对象
在写CURD的类中先创建一个CURD共享的SQL执行对象QueryRunner
// 创建SQL执行对象
private QueryRunner qr = new QueryRunner(JdbcUtil.getDataSource());
3.3.4.2 添加
代码 :
/**
* 添加人员信息
* @param person
* @return
* @throws Exception
*/
public int insertPerson(Person person)throws Exception{
// 创建方法的返回值
int count = 0;
// 编写SQL语句
String sql = "insert into person values (null,?,?,?,?)";
// 占位符赋值
Object[] params = {person.getName(),person.getSex(),person.getAge(),person.getFrom()};
// 执行
count = qr.update(sql,params);
// 返回
return count;
}
测试:
@Test
public void test()throws Exception{
// 创建要添加的数据
Person person = new Person();
person.setSex("女");
person.setAge(18);
person.setFrom("湖南省");
person.setName("王小花");
int count = this.insertPerson(person);
String msg = count>0?"添加成功":"添加失败";
System.out.println(msg);
}
效果:
3.3.4.3 按id查询
代码 :
/**
* 根据ID查询数据
* @param id
* @return
* @throws Exception
*/
public Person findPersonByID(Integer id)throws Exception{
// 创建方法的返回值
Person person = null;
// 编写SQL语句
String sql = "select * from person where `id` = ?";
// 占位符赋值
Object[] params = {id};
// 执行
person = qr.query(sql, new BeanHandler<Person>(Person.class),params);
// 返回
return person;
}
说明 :
此处我们在查询的时候应用了query(sql语句,返回值设置,sql语句的占位符);
方法编码中 new BeanHandler<Person>(Person.class)
是设置本次SQL执行返回一个BeanHandler
BeanHandler是啥呢?
BeanHandler设置后表示这条SQL返回一个JavaBean对象,
但是到底返回哪个JavaBean的对象需要自己设置
此处我们就是把BeanHandler转换为Person对象进行返回的,
对于BeanHandler的设置是分两步完成
BeanHandler<Person>
通过泛型设置了BeanHandler的类型- 然后Person.class指定了这个BeanHandler应该返回Person类的对象
Person.class是获取Person的class对象。
下面给你说几种获取class对象的方法:
-
所有的引用数据类型(类-类型)的类名、基本数据类型都可以通过.class方式获取其 Class对象
(对于基本数据类型的封装类还可以通过.TYPE 的方式获取其 Class 对象。
但要注意,TYPE 实际上获取的封装类对应的基本类型的 Class 对象的引用,
那么你可以判断出
int.class==Integer.TYPE
返回 true,
int.class==Integer.class
返回 false!)
通过这种方式不会初始化静态域,使用.class、.TYPE 的方式获取 Class对象叫做类的字面常量; -
Class 的
forName(String name)
传入一个类的完整类路径也可以获得 Class 对象,
但由于使用的是字符串,必须强制转换才可以获取泛型的Class<T>
的 Class对象,
并且你必须获取这个方法可能抛出的ClassNotFoundException
异常。
这种方法可以初始化静态域。 -
还可通过类的对象实例下的
getClass()
方法来获取Class对象,即 实例名.getClass()
测试:
@Test
public void test()throws Exception{
Person person = this.findPersonByID(5);
System.out.println(person);
}
效果:
3.3.4.4 更新
代码 :
/**
* 更新人员信息
* @param person
* @return
* @throws Exception
*/
public int updatePerson(Person person)throws Exception{
// 创建方法的返回值
int count = 0;
// 编写SQL语句
String sql = "update person set `name`=?,`sex`=?,`age`=?,`from`=? where `id`=?";
// 占位符赋值
Object[] params = {person.getName(),person.getSex(),person.getAge(),person.getFrom(),person.getId()};
// 执行
count = qr.update(sql,params);
// 返回
return count;
}
测试:
@Test
public void test()throws Exception{
// 更新测试
Person person = this.findPersonByID(5);
person.setName("李建军");
person.setSex("男");
person.setAge(35);
person.setFrom("广东省");
int count = this.updatePerson(person);
String msg = count>0?"更新成功":"更新失败";
System.out.println(msg);
}
效果:
3.3.4.5 删除
代码 :
/**
* 删除人员信息
* @param person
* @return
* @throws Exception
*/
public int deletePerson(Person person)throws Exception{
// 创建方法的返回值
int count = 0;
// 编写SQL语句
String sql = "delete from person where `id` = ?";
// 占位符赋值
Object[] params = {person.getId()};
// 执行
count = qr.update(sql,params);
// 返回
return count;
}
测试:
@Test
public void test()throws Exception{
// 更新测试
Person person = this.findPersonByID(5);
int count = this.deletePerson(person);
String msg = count>0?"删除成功":"删除失败";
System.out.println(msg);
}
效果:
3.3.4.6 查询所有
代码 :
/**
* 查询所有人员信息
* @return
* @throws Exception
*/
public List<Person> findAllPersons()throws Exception{
// 创建方法的返回值
List<Person> list = null;
// 编写SQL语句
String sql = "select * from person";
// 占位符赋值
// 执行
list = qr.query(sql, new BeanListHandler<Person>(Person.class));
// 返回
return list;
}
测试:
@Test
public void test()throws Exception{
List<Person> list = this.findAllPersons();
// 遍历
for (Person person : list) {
System.out.println(person);
}
}
效果:
到此基本的CURD案例完成
3.3.5 分页效果实现
3.3.5.1 添加测试数据
向数据库中添加300条人员信息
@Test
public void test()throws Exception{
Person p = null;
Random r = new Random();
for (int i = 1; i <= 300; i++) {
p = new Person();
p.setName("普通用户"+i);
// 随机添加男 或 女
if(r.nextInt(2) == 0){
p.setSex("女");
}else{
p.setSex("男");
}
p.setAge(r.nextInt(100)==0?1:r.nextInt(100));
p.setFrom("山东省");
System.out.println(this.insertPerson(p));
}
}
效果:
3.3.5.2 总记录数
代码 :
/**
* 获取总记录数
* @return
* @throws Exception
*/
public int findPersonsByPageCount()throws Exception{
// 创建方法的返回值
int totalCount = 0;
// 编写SQL语句
String sql = "select count(*) from person";
// 占位符赋值
// 执行
totalCount = qr.query(sql, new ScalarHandler<Integer>());
// 返回
return totalCount;
}
测试
@Test
public void test()throws Exception{
int totalCount = this.findPersonsByPageCount();
System.out.println(totalCount);
}
效果:
报错!报错信息提示你
Long类型的返回值不能转换为Integer
也就是说select count(*) from person 这条SQL语句 dbutils 把这个数当做Long类型处理
,所以我们需要修改下查询全部的方法
/**
* 获取总记录数
* @return
* @throws Exception
*/
public int findPersonsByPageCount()throws Exception{
// 创建方法的返回值
int totalCount = 0;
// 编写SQL语句
String sql = "select count(*) from person";
// 占位符赋值
// 执行
Number num = qr.query(sql, new ScalarHandler<Number>());
// 处理返回值
totalCount = num.intValue();
// 返回
return totalCount;
}
重新执行单元测试效果
拓展学习 java.lang.Number
java.lang.Number这个基础类,看似貌不惊人,
其实在java数字类型生态系统中很重要。上图看下他的子类家族
3.3.5.3 分页展示数据
代码 :
/**
* 分页操作
* @param start
* @param size
* @return
* @throws Exception
*/
public List<Person> findPersonsByPage(Integer start,Integer size)throws Exception{
// 创建方法的返回值
List<Person> persons = null;
// 编写SQL语句
String sql = "select * from person limit ?,?";
// 占位符赋值
Object[] params = {start,size};
// 执行
persons = qr.query(sql, new BeanListHandler<Person>(Person.class),params);
// 返回
return persons;
}
测试:
@Test
public void test()throws Exception{
int pageIndex = 1; // 页码
int pageSize = 10; // 页容量
// 总条数
int totalCount = this.findPersonsByPageCount();
// 总页数
int totalPage = totalCount%pageSize==0?totalCount/pageSize:totalCount/pageSize+1;
// 输出
System.out.println("总条数"+totalCount+"条,每页展示"+pageSize+"条,一共分"+totalPage+"页,当前为第"+pageIndex+"页;");
// 获取分页集合
List<Person> list = this.findPersonsByPage((pageIndex-1)*pageSize, pageSize);
// 遍历循环
for (Person person : list) {
System.out.println(person);
}
}
效果: