5. 数据库连接池
5.1 概念
前面在 JDBC API 详解中,获取的数据库连接对象 conn,在使用时创建,使用完毕就会将其销毁。这样重复创建和销毁的过程实际上是消耗性能和时间的,当大量用户访问数据库时,每次都要进行数据库连接对象的创建和销毁,对系统来说是一种大量的消耗。那么我们怎样来提升性能和节省时间呢?
我们使用数据库连接池来重复利用数据库的连接对象,即 Connection 类对象。
数据库连接池是一个负责分配,管理数据库连接对象的容器,它允许应用程序重复使用同一个数据库连接对象。数据库连接池可以释放空闲时间超过最大空闲时间的数据库连接对象来避免因为没有释放数据库连接对象而引起的数据库连接遗漏。
连接池是在一开始就创建好了一些连接对象存储起来,用户需要连接数据库时,不需要自己创建连接对象,而只需要从连接池中获取一个连接对象进行使用,使用完毕后再将连接对象归还给连接池。这样就可以起到资源重用的作用,也节省了频繁创建连接销毁连接所花费的时间,从而提升了系统响应的速度。
总结来说使用数据库连接池有以下几点好处:
- 实现资源重用
- 提升系统响应速度
- 避免数据库连接遗漏
5.2 实现
sun 公司提供了数据库连接池的标准接口 DataSource ,我们一般使用第三方实现该接口的实现类,所有实现类都继承了其获取连接的方法:
Connection getConnection()
常见的数据库连接池有:
- Druid(德鲁伊)
- C3P0
- DBCP
使用了数据库连接池以后,在获取数据库连接对象时不需要通过 DriverManager类的 getConnection() 方法,而是直接从数据库连接池中获取。我们今天要使用的是 Druid 连接池,它是阿里巴巴开源的数据库连接池项目,其功能强大,性能优秀,使用占比高,是一款优秀的数据库连接池。
6. Druid 连接池的使用
下面以 Druid 连接池为例,讲解通过数据库连接池获取数据库连接对象,主要有以下几个步骤:
1.导入Druid 连接池的 jar 包
2.定义配置文件
3.加载配置文件
4.获取数据库连接池对象
5.获取连接
第一步:将 Druid 的 jar 包放入项目中的 Lib 目录下作为库文件,jar 包自行下载。
选择 Add as Library,选择模块下有效:
第二步:编写配置文件,在 src 目录下创建文件 druid.properties 。
driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql:///db1?useSSL=false&useServerPrepStmts=true username=root password=1234 # 初始化连接数量 initialSize=5 # 最大连接数 maxActive=10 # 最大等待时间 maxWait=3000
Druid 配置文件中有很很多的参数,这里配置了用到的几项,其中有连接数据库的名称和密码,初始连接数,最大连接数,最大等待时间等,超过了最大等待时间配置文件还没有加载成功的话,程序就会报错。
第三步:在代码中加载配置文件
//3. 加载配置文件 Properties prop = new Properties(); prop.load(new FileInputStream("jdbc-demo/src/druid.properties"));
第四步:在代码中获取连接池对象
//4. 获取连接池对象 DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
第五步:获取数据库连接对象
//5. 获取数据库连接 Connection Connection connection = dataSource.getConnection(); System.out.println(connection); //获取到了连接后就可以继续做其他操作了
示例,Druid的使用完整代码:
public class DruidDemo { public static void main(String[] args) throws Exception { //1.导入jar包 //2.定义配置文件 //3. 加载配置文件 Properties prop = new Properties(); prop.load(new FileInputStream("jdbc-demo/src/druid.properties")); //4. 获取连接池对象 DataSource dataSource = DruidDataSourceFactory.createDataSource(prop); //5. 获取数据库连接 Connection Connection connection = dataSource.getConnection(); System.out.println(connection); //获取到了连接后就可以继续做其他操作了 } }
运行结果:
其中,DruidDataSourceFactory 类中的 createDataSource() 方法既可以传入一个 Map 集合,也可以传入 prop 对象,其中都存放配置信息,用来获取连接池对象。
小tips:如果代码中文件的相对路径报错,可以使用 System.getProperty("user.dir") 获取项目的当前路径。
7. 准备工作
前面我们说过,在 Java 代码中,执行 sql 查询语句以后返回一个 ResultSet 类的对象,并且 ResultSet 类提供了方法让我们可以操作查询结果数据,例如可以直接打印所有查询的数据。
示例:
public class JDBCDemo { public static void main(String[] args) throws Exception { //1. 注册驱动 Class.forName("com.mysql.jdbc.Driver"); //2. 获取连接 String url="jdbc:mysql://localhost:3306/blog?useSSL=false"; String username="root"; String ppassword="abc123";//密码 Connection conn = DriverManager.getConnection(url,username,password); //3. 定义sql String sql="select * from student"; //4. 获取sql执行对象 Statement stmt = conn.createStatement(); //5. 执行sql ResultSet rs = stmt.executeQuery(sql); //6. 处理结果 while (rs.next()){ int id=rs.getInt(1); String name=rs.getString(2); String gender=rs.getString(3); System.out.println(id); System.out.println(name); System.out.println(gender); System.out.println("---------"); } //7. 释放资源 rs.close(); stmt.close(); conn.close(); } }
运行结果:
显然这样并不是我们想要的效果,数据零散不好处理。所以我们可以把每条数据封装为一个实体类的对象,每个对象对应数据库表的一条记录,并且把每个对象放到集合中存储。练习使用的数据库表:
drop table if exists student; create table student( id int primary key auto_increment, name varchar(10), gender char(1) ); insert into student(name,gender) values ('张三','男'), ('李四','女'), ('王五','男');
在 Navicat 中查看数据表:
8. 实战案例
查询学生信息表数据,封装为 Student 类对象,并存放在 ArrayList 集合中。
思路:要解决这个问题,大概分为以下几个步骤:
- 创建一个 Student 实体类
- 查询数据库中的数据,并且赋值给对象的属性
- 将对象存储到 ArrayList 集合中
第一步:创建一个Student 实体类,并定义 set() 方法,重写 Object 类中的 toString 方法,方便查看打印效果。
public class Student { private int id; private String name; private String gender; public int getId() { return id; } public void setId(int id) { this.id = id; } public void setName(String name) { this.name = name; } public void setGender(String gender) { this.gender = gender; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", gender='" + gender + '\'' + '}'; } }
小tips :在 idea 中使用 Alt + Inset 快捷键可以快速给类添加或重写一些方法,例如 get() ,set() ,toString() 方法等。使用 Ctrl + 鼠标左键可以快速选择多项方法。
第二步:使用 Java 代码操作数据库,查询数据库表中所有学生信息并通过 set() 方法赋值给 Student 类的对象,将对象存储到集合中。
public class JDBCDemo { public static void main(String[] args) throws Exception { //1. 注册驱动 Class.forName("com.mysql.jdbc.Driver"); //2. 获取连接 String url = "jdbc:mysql://localhost:3306/blog?useSSL=false"; String username = "root"; String ppassword = "abc123";//密码 Connection conn = DriverManager.getConnection(url, username, password); //3. 定义sql String sql = "select * from student"; //4. 获取sql执行对象 Statement stmt = conn.createStatement(); //5. 执行sql ResultSet rs = stmt.executeQuery(sql); //6. 处理结果 List<Student> students = new ArrayList<>(); while (rs.next()) { Student s = new Student(); int id = rs.getInt(1); String name = rs.getString(2); String gender = rs.getString(3); s.setId(id); s.setName(name); s.setGender(gender); students.add(s); } System.out.println(students); //7. 释放资源 rs.close(); stmt.close(); conn.close(); } }
运行结果:
public class JDBCDemo { public static void main(String[] args) throws Exception { //1. 注册驱动 Class.forName("com.mysql.jdbc.Driver"); //2. 获取连接 String url = "jdbc:mysql://localhost:3306/blog?useSSL=false"; String username = "root"; String ppassword = "abc123";//密码 Connection conn = DriverManager.getConnection(url, username, password); //3. 定义sql String sql = "select * from student"; //4. 获取sql执行对象 Statement stmt = conn.createStatement(); //5. 执行sql ResultSet rs = stmt.executeQuery(sql); //6. 处理结果 List<Student> students = new ArrayList<>(); while (rs.next()) { Student s = new Student(); int id = rs.getInt(1); String name = rs.getString(2); String gender = rs.getString(3); s.setId(id); s.setName(name); s.setGender(gender); students.add(s); } System.out.println(students); //7. 释放资源 rs.close(); stmt.close(); conn.close(); } }
这样,我们的程序就达到了预期的效果,将来需要使用的数据全部封装为对象并存放在了集合中。
9. 增删改查操作练习
在数据库连接池入门篇中,我们学习了 Druid 连接池的使用,数据库连接池允许重复使用一个现有的数据库连接对象,提升了系统的响应速度和时间。下面我们使用数据库连接池来练习解决上面的问题,并且在获取 sql 执行对象时,我们使用 PreparedStatement 类,解决sql 注入的问题。
9.1 查询所有
查询所有数据,并把查询结果数据封装为对象存储在集合中,这里的数据表,Student 实体类和上面例子中相同。
public class DruidDemo { public static void main(String[] args) throws Exception { //加载配置文件 Properties prop = new Properties(); prop.load(new FileInputStream("jdbc-demo/src/druid.properties")); //获取数据库连接池对象 DataSource dataSource = DruidDataSourceFactory.createDataSource(prop); //获取数据库连接对象 Connection conn = dataSource.getConnection(); //定义sql String sql = "select * from student"; //获取 sql 执行对象 PreparedStatement pstmt = conn.prepareStatement(sql); //执行 sql ResultSet rs = pstmt.executeQuery(); //处理数据,将查询结果数据封装为对象存储在集合中 List<Student> students = new ArrayList<>(); while (rs.next()) { Student s = new Student(); int id = rs.getInt(1); String name = rs.getString(2); String gender = rs.getString(3); s.setId(id); s.setName(name); s.setGender(gender); students.add(s); } System.out.println(students); //释放资源 rs.close(); pstmt.close(); conn.close(); } }
9.2 添加数据
现在演示往数据库中添加一条记录,应用场景为用户在客户端输入一条数据时,我们需要将数据添加到数据库。示例:
public class DruidDemo { public static void main(String[] args) throws Exception { //接收到用户的数据 int id=4; String name="小美"; String gender="女"; //加载配置文件 Properties prop = new Properties(); prop.load(new FileInputStream("jdbc-demo/src/druid.properties")); //获取数据库连接池对象 DataSource dataSource = DruidDataSourceFactory.createDataSource(prop); //获取数据库连接对象 Connection conn = dataSource.getConnection(); //定义sql String sql = "insert into student values(?,?,?)"; //获取 sql 执行对象 PreparedStatement pstmt = conn.prepareStatement(sql); //设置参数 pstmt.setInt(1,id); pstmt.setString(2,name); pstmt.setString(3,gender); //执行 sql int count = pstmt.executeUpdate();//返回受影响的行数 //处理数据 if(count>0){ System.out.println("添加成功"); }else{ System.out.println("添加失败"); } //释放资源 pstmt.close(); conn.close(); } }
此时的数据表:
9.3 修改数据
现在数据表中的数据,应用场景为用户在客户端修改数据,对应数据库中的数据也要完成修改。示例:
public class DruidDemo { public static void main(String[] args) throws Exception { //接收到用户的数据 int id=1; String name="小王"; String gender="女"; //加载配置文件 Properties prop = new Properties(); prop.load(new FileInputStream("jdbc-demo/src/druid.properties")); //获取数据库连接池对象 DataSource dataSource = DruidDataSourceFactory.createDataSource(prop); //获取数据库连接对象 Connection conn = dataSource.getConnection(); //定义sql String sql = "update student set name=?,gender=? where id=?"; //获取 sql 执行对象 PreparedStatement pstmt = conn.prepareStatement(sql); //设置参数 pstmt.setString(1,name); pstmt.setString(2,gender); pstmt.setInt(3,id); //执行 sql int count = pstmt.executeUpdate();//返回受影响的行数 //处理数据 if(count>0){ System.out.println("修改成功"); }else{ System.out.println("修改失败"); } //释放资源 pstmt.close(); conn.close(); } }
此时的数据表:
9.4 删除数据
下面演示删除数据,用户在客户端选择删除某条数据记录时,数据库中的数据也要完成删除操作。示例:
public class DruidDemo { public static void main(String[] args) throws Exception { //接收到用户的数据 int id=4; //加载配置文件 Properties prop = new Properties(); prop.load(new FileInputStream("jdbc-demo/src/druid.properties")); //获取数据库连接池对象 DataSource dataSource = DruidDataSourceFactory.createDataSource(prop); //获取数据库连接对象 Connection conn = dataSource.getConnection(); //定义sql String sql = "delete from student where id=?"; //获取 sql 执行对象 PreparedStatement pstmt = conn.prepareStatement(sql); //设置参数 pstmt.setInt(1,id); //执行 sql int count = pstmt.executeUpdate();//返回受影响的行数 //处理数据 if(count>0){ System.out.println("删除成功"); }else{ System.out.println("删除失败"); } //释放资源 pstmt.close(); conn.close(); } }
此时的数据表:
10. 总结
学习完这篇文章,你将学会使用 Java 代码操作数据库,包括数据库的连接,创建,数据的增删改查等操作。
JDBC 作为基础性的代码,在开发中其实存在着很多的缺点,所以在开发中并不会直接使用,而一般会使用框架开发,使用框架开发不仅能够提高开发的效率,而且大大提高了代码的规范性。MyBatis 就是一款优秀的持久层框架,专门用来简化 JDBC 开发,后面我们马上会接触到,但是在这之前,一定要先熟练 JDBC 的使用。