今日内容
1. 数据库连接池
2. Spring JDBC : JDBC Template
1、数据库连接池
概念:其实就是一个容器(集合),存放数据库连接的容器。 当系统初始化好后,容器被创建,容器中会申请一些连接对象,当用户来访问数据库时,从容器中获取连接对象,用户访问完之后,会将连接对象归还给容器。
好处
1. 节约资源
2. 用户访问高效
获取数据库连接其实就是向操作系统底层获取资源,这种获取资源的方式其实是十分耗时的。
实现:
1. 标准接口:DataSource javax.sql包下的
sun公司没有给DataSource接口提供实现类,DataSource接口由驱动程序供应商(既数据库厂商实现)实现。
1. 方法:
* 获取连接:getConnection()
* 归还连接:Connection.close()。如果连接对象Connection是从连接池中获取的,那么调用Connection.close()方法,则不会再关闭连接了。而是归还连接
2. 一般我们不去实现它,有数据库厂商来实现
1. C3P0:数据库连接池技术
2. Druid:数据库连接池实现技术,由阿里巴巴提供的
C3P0:数据库连接池技术
步骤:
1. 导入jar包 (两个) c3p0-0.9.5.2.jar mchange-commons-java-0.2.12.jar(依赖jar包) ,
* 不要忘记导入数据库驱动jar包
2. 定义配置文件:
* 名称: c3p0.properties 或者 c3p0-config.xml(只有把配置文件命名成为这两个名字,系统才能找到)
* 路径:直接将文件放在src目录下即可。
3. 创建核心对象 数据库连接池对象 ComboPooledDataSource
4. 获取连接: getConnection
我们想使用别人提供的数据库连接池,别人是实现DataSource对象创建这个连接池的。我们首先需要导入别人的实现jar包,如这里导入了C3P0数据库连接池的2个jar包,这样我们就可以使用数据库连接池的功能。
(注意导入数据库驱动jar包,因为我们始终都是要获取数据库的连接,那么就必须要驱动数据库,那么就必须获取数据库驱动jar包),另外,需要注意导入的jar包必须添加到当前项目的类库(Add as library)。
其次,我们想要获取C3P0的jar包所提供的数据库连接,获取数据库连接对象有2种方法(如C3P0文档介绍),硬编码的形式(像之前一样获取连接)不推荐,还有一种是通过配置文件获取。文档说明如下:
Configuration files are normally looked up under standard names (c3p0.properties or c3p0-config.xml) at the top level of an application's classpath
配置文件一般命名为“c3p0.properties”或者“c3p0-config.xml”,且系统会自动在当前应用的classpath最顶层路径下面取寻找这两个文件,而我们的src包的路径会默认放到classpath下,因此我们将数据库连接池的配置文件放到src包的路径下即可,这样在使用的时候系统就能通过classpath找到配置文件。
这个配置文件就类似于我们之前所使用的“jdbc.properties”这个配置文件,同样是从配置文件来获取数据库连接,而我们所导入的C3P0的jar包,提供的是数据库连接池底层的实现。
接下来数据库连接池对象 ComboPooledDataSource,然后就可以获取数据库连接。
代码:
package lkj.test.C3P0Demo;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* c3p0的演示
*/
public class C3P0Demo1
{
public static void main(String[] args) throws SQLException
{
//我们导入C3P0的jar包以及数据库驱动包,并导入C3P0的配置文件之后,就可以开始获取数据库连接对象
//数据库注册驱动C3P0jar包以及配置文件已经在底层完成,我们这里可以直接使用CombopooledDataSource对象来获取数据库连接池的数据库连接对象。
//1.创建数据库连接池对象
DataSource ds = new ComboPooledDataSource();
//2. 获取连接对象
Connection con = ds.getConnection();
System.out.println(con);
//com.mchange.v2.c3p0.impl.NewProxyConnection@ba4d54 [wrapping: com.mysql.jdbc.JDBC4Connection@12bc6874]
}
}
接下来我们对配置文件c3p0-config.xml进行解析。
package lkj.test.C3P0Demo;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.mchange.v2.c3p0.DataSources;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* c3p0-config.xml文件配置信息的验证
*/
public class C3P0Demo1
{
public static void main(String[] args) throws SQLException
{
testNamedConfig();
//验证连接池参数 ——最大的连接数量(这里会使用第一个默认配置)
//同样先创建数据库连接池对象(注意是DataSource不是DataSources)
// DataSource ds = new ComboPooledDataSource();
// for (int i = 0; i < 11; i++)
// {
// Connection con = ds.getConnection();
// //设置数据库连接池最大连接数量也10,全部获取到
// System.out.println(i+" : "+con);
//
// //如果我们尝试获取11个,获取到10个之后,等待延时3秒之后就会报错
// }
// for (int i = 0; i < 11; i++)
// {
// Connection con = ds.getConnection();
// System.out.println(i+" : "+con);
//如果我们想获取11个,我们可以用connection的close方法,归还连接
// if(i==5)
// {
//我们将i=5时的所获取的数据库连接归还给连接池,这样就可以第11次获取数据库连接
//注意,只在i=5时归还一个连接,因此没办法第12次获取
// con.close();//归还连接到连接池中
// }
// }
}
//named-config:匿名配置的验证
//多个配置使得我们可以在获取连接的时候使用不同的config配置,使得使用更加方便
public static void testNamedConfig() throws SQLException
{
//获取数据库连接池对象(使用匿名配置)
DataSource ds = new ComboPooledDataSource("otherc3p0");
for (int i = 0; i < 9; i++)
{
//匿名“otherc3p0”配置设置的最大连接数是8,尝试获取9个连接会报错
//使用的时候注意指定好数据库为db2
Connection con = ds.getConnection();
System.out.println(i + " : " + con);
}
}
}
Druid:数据库连接池实现技术,由阿里巴巴提供的
步骤:
1. 导入jar包 druid-1.0.9.jar(同样记得导入数据库驱动jar包)
2. 定义配置文件:
* 是properties形式的
* 可以叫任意名称,可以放在任意目录下(因为可以叫任意名称,可以任意加载,说明系统不会像C3P0一样自动读取配置文件,而需要我们手动加载)
3. 加载配置文件。Properties
4. 获取数据库连接池对象:通过工厂来来获取 DruidDataSourceFactory的createDataSource(new Properties)方法,参数是加载了相应配置文件读取流的Properties对象。
5. 获取连接:getConnection
代码:
package lkj.test.druidDemo;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;
/**
* Druid演示
*/
public class DruidDemo
{
public static void main(String[] args) throws Exception
{
//1、导入druid的jar包(注意Add as library,同时导入数据库驱动jar包)
//2、定义Druid相关配置文件——我们同样将druid.properties放在src下
//3、加载配置文件(这里需要手动加载,系统不会自动加载)
/*
DruidDataSourceFactory.createDataSource加载的参数是Properties
我们获取DruidDemo类的类加载器Classloader,ClassLoader里面有一个可以通过对应配置文件绝对路径
直接获取该配置文件字节读取流的方法getResourceAsStream.
我们之前在JDBCUtil工具类中是先使用getResource配置文件的URL,再通过URL获取文件的绝对路径,再通过文件的绝对路径获取文件读取流
之前的方法比较麻烦,现在简化很多
*/
Properties pro = new Properties();
//由于druid.properties放在src目录下面,我们的DruidDemo类可以找到它
InputStream rs = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties");
pro.load(rs);
//4.获取连接池对象
//数据库注册驱动druid已经在底层实现,我们这里获取数据库连接后可以直接使用
DataSource ds = DruidDataSourceFactory.createDataSource(pro);
//5、获取数据库连接
Connection con = ds.getConnection();
System.out.println(con);//com.mysql.jdbc.JDBC4Connection@2280cdac
}
}
为了方便编程,我们同样定义一个工具类
1. 定义一个类 JDBCUtils
2. 提供静态代码块加载配置文件,初始化连接池对象
3. 提供方法
1. 获取连接方法:通过数据库连接池获取连接
2. 释放资源
3. 获取连接池的方法
相应代码如下
//------------------------------JDBCUtil工具类
package lkj.test.druidDemo;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
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 JDBCUtil
{
private static DataSource ds = null;
//首先通过一个静态代码块,获取druid连接池对象,同时druid底层注册数据库驱动
static
{
try
{
Properties pro = new Properties();
InputStream rs = JDBCUtil.class.getClassLoader().getResourceAsStream("druid.properties");
pro.load(rs);
ds = DruidDataSourceFactory.createDataSource(pro);
}
catch (IOException e)
{
e.printStackTrace();
}
catch (Exception e)
{
e.printStackTrace();
}
}
/**
* 获取连接
*/
public static Connection getConnection() throws SQLException
{
return ds.getConnection();
}
/**
* 获取连接池方法
*/
public static DataSource getDataSource()
{
return ds;
}
/**
* 释放资源(释放3个资源的方法)
* @param con
* @param sm
* @param rs
*/
public static void close(Connection con , Statement sm,ResultSet rs)
{
if(rs!=null)
{
try
{
rs.close();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
if(sm!=null)
{
try
{
sm.close();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
if(con!=null)
{
try
{
con.close();//不是释放连接,而是向数据库连接池归还连接
}
catch (SQLException e)
{
e.printStackTrace();
}
}
}
/**
* 释放资源(释放2个资源的方法,调用释放3个资源的方法)
* 没有使用DQL,所以不需要用到ResultSet
* @param sm
* @param con
*/
public static void close(Connection con , Statement sm)
{
close(con,sm,null);
}
}
//----------------------------主类DruidDemo
package lkj.test.druidDemo;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* 使用新的工具类,直接演示JDBC
*/
public class DruidDemo
{
public static void main(String[] args)
{
//数据库驱动jar已经导入,druid底层已经注册驱动
Connection con = null;
PreparedStatement ps = null;
try
{
//获取数据库连接对象
con = JDBCUtil.getConnection();
//创建sql语句
String sql = "insert into account values(null,?,?) ";
//获取数据库执行对象
ps = con.prepareStatement(sql);//注意这里参数是SQL语句
//为SQL语句占位符赋值
ps.setString(1,"wangwu");
ps.setInt(2,666);
//执行SQL语句
int count = ps.executeUpdate();
System.out.println(count);
}
catch(SQLException e)
{
e.printStackTrace();
}
finally
{
//关闭资源
JDBCUtil.close(con,ps);
}
//返回结果1,添加成功
}
}
2、JDBCTemplate
Spring框架对JDBC的简单封装。提供了一个JDBCTemplate对象简化JDBC的开发。
步骤:
1. 导入jar(5个)
2. 创建JdbcTemplate对象。依赖于数据源DataSource
* JdbcTemplate template = new JdbcTemplate(ds);
JdbcTemplate需要使用到连接池对象ds,这里我们可以通过C3P0获取DataSource对象,也可以通过Druid获取连接池对象。在获取连接池对象的同时,我们通过连接池的配置文件同时给数据库注册驱动。
3. 调用JdbcTemplate的方法来完成CRUD的操作
* update():执行DML语句。增、删、改语句
* queryForMap():查询结果将结果集封装为map集合,将列名作为key,将值作为value 将这条记录封装为一个map集合
* 注意:这个方法查询的结果集长度只能是1
* queryForList():查询结果将结果集封装为list集合
* 注意:将每一条记录封装为一个Map集合,再将Map集合装载到List集合中
* query():查询结果,将结果封装为JavaBean对象
* query的参数:RowMapper
* 一般我们使用BeanPropertyRowMapper实现类。可以完成数据到JavaBean的自动封装
* new BeanPropertyRowMapper<类型>(类型.class)
* queryForObject:查询结果,将结果封装为对象
* 一般用于聚合函数的查询
注意,IDEA中CTRL+p快捷键可以查看这个地方需要插入说明类型的数据。
示例代码
package lkj.test.JDBCTemplate;
import lkj.test.druidDemo.JDBCUtil;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
import java.sql.SQLException;
public class JdbcTemplateDemo//注意类名不要取JdbcTemplate,容易出错
{
public static void main(String[] args) throws SQLException
{
//1、首先是导入JDBCTemplate所需的5个jar包,注意将这些包Add as library
//同时注意导入Druid连接池的包以及数据库驱动的包并Add as Library
/*JdbcTemplate对象是由JDBCTemplate的jar包提供的对象,java原文档里面没有
2、获取JdbcTemplate对象,参数是DataSource对象(注意是Druid数据库连接池对象,而不是数据库连接)
我们这里通过之前的工具类JDBCUtil获取,使用的是Druid连接池
Druid连接池通过配置文件,在底层注册了数据库的驱动,我们不需要再手动注册驱动
*/
//不是getConnection获取数据库连接,而是getDataSource
JdbcTemplate jt = new JdbcTemplate(JDBCUtil.getDataSource());
//定义SQL语句(注意在Druid的配置文件druid.properties中将操作的数据库设置为account表所在的数据库)
String sql = "update account set balance = 5000 where id= ?";
//通过JdbcTemplate的update方法执行SQL语句(获取连接,获取执行,占位符赋值,执行4步一起执行)
//我们可以通过这个方法直接为SQL的占位符赋值并执行SQL语句
//之前是先获取数据库连接对象,再通过连接获取数据库执行对象PreparedStatement(传入SQL语句),
// 再利用这个对象的方法为占位符赋值,再使用对象的方法执行
int count = jt.update(sql,2);//第二个参数为占位符赋值
System.out.println(count);
/*
使用JDBCTemplate,我们使用Druid数据库连接池,它完成了数据库驱动的注册
然后我们获取数据库连接池的对象,放入JDBCTemplate中,再直接执行SQL语句(同时为占位符赋值)
JDBCTemplate帮我们将获取数据库连接、获取数据库执行、SQL语句赋值SQL语句执行全部完成
同时,JDBCTemplate也会将资源全部释放。(5步全部执行)
我们专注于使用JDBCTemplate即可,其他的知道就可以
*/
}
}
练习
需求:
1. 修改1号数据的 salary 为 10000
2. 添加一条记录
3. 删除刚才添加的记录
4. 查询id为1的记录,将其封装为Map集合
5. 查询所有记录,将其封装为List
6. 查询所有记录,将其封装为Emp对象的List集合
7. 查询总记录数
相应的代码如下
//---------------------Emp类
package lkj.test.JDBCTemplate;
import java.util.Date;
public class Emp
{
//注意我们这里将所有的属性定义为引用数据类型,以后表中如果读取出的数据是null,引用数据类型才可以接收
//如果定义为基本数据类型则无法接收。如我们把bonus定义为double
//Property 'bonus' threw exception; nested exception is java.lang.NullPointerException
private Integer id;
private String ename;
private Integer job_id;
private Integer mgr;
private Date joindate;
private Double salary;
private Double bonus;
private Integer dept_id;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public Integer getJob_id() {
return job_id;
}
public void setJob_id(Integer job_id) {
this.job_id = job_id;
}
public Integer getMgr() {
return mgr;
}
public void setMgr(Integer mgr) {
this.mgr = mgr;
}
public Date getJoindate() {
return joindate;
}
public void setJoindate(Date joindate) {
this.joindate = joindate;
}
public Double getSalary() {
return salary;
}
public void setSalary(Double salary) {
this.salary = salary;
}
public Double getBonus() {
return bonus;
}
public void setBonus(Double bonus) {
this.bonus = bonus;
}
public Integer getDept_id() {
return dept_id;
}
public void setDept_id(Integer dept_id) {
this.dept_id = dept_id;
}
@Override
public String toString() {
return "Emp{" +
"id=" + id +
", ename='" + ename + '\'' +
", job_id=" + job_id +
", mgr=" + mgr +
", joindate=" + joindate +
", salary=" + salary +
", bonus=" + bonus +
", dept_id=" + dept_id +
'}';
}
}
//---------------------------测试类JdbcTemplateDemo2
package lkj.test.JDBCTemplate;
import lkj.test.druidDemo.JDBCUtil;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class JdbcTemplateDemo2
{
//Junit单元测试,可以让方法独立执行(方便测试)
//1. 获取JDBCTemplate对象(同样使用的是db1数据库,不需要修改Druid的配置文件)
private JdbcTemplate jt = new JdbcTemplate(JDBCUtil.getDataSource());
/**
* 1. 修改1号数据的 salary 为 10000
*/
@Test
public void test1()
{
//2、定义SQL语句
String sql = "update emp set salary = 10000 where id = 1001";
//3、执行SQL
int count = jt.update(sql);
Assert.assertEquals(1,count);//结果是绿色,修改成功
}
/**
* 2. 添加一条记录
*/
@Test
public void test2()
{
String sql = "insert into emp(id,ename,dept_id) values(?,?,?)";//我们插入一些数据并为某些列赋值,没有赋值的列会设置为null
int count = jt.update(sql,1015,"王思聪",10);
Assert.assertEquals(1,count);//结果是绿色,修改成功
}
/**
* 3.删除刚才添加的记录
*/
@Test
public void test3()
{
String sql = "delete from emp where id = ?";
int count = jt.update(sql,1015);
Assert.assertEquals(1,count);//结果是绿色,修改成功
}
//--------------------------------------------------------------------下面是DQL语句
/**
* 4.查询id为1001的记录,将其封装为Map集合
* 注意:这个方法查询的结果集长度只能是1
*/
@Test
public void test4()
{
String sql = "select * from emp where id = ? or id = ?";
//查询结果将结果集封装为map集合,将列名作为key,将值作为value 将这条记录封装为一个map集合
//因此Map的泛型第一个是列名String,第二个为了表示多种类型的值,设置为Object
Map<String, Object> map = jt.queryForMap(sql, 1001,1002);
System.out.println(map);
//{id=1001, ename=孙悟空, job_id=4, mgr=1004, joindate=2000-12-17, salary=10000.00, bonus=null, dept_id=20}
//如果我们尝试查询2行,查询出来的Map结果集的长度为2,就会出错。注意 queryForMap 查询的结果集长度只能是1,既只能查询一行
}
/**
* 5. 查询所有记录,将其封装为List
*/
@Test
public void test5()
{
String sql = "select * from emp";
//queryForList将每一条记录封装为一个Map集合,再将Map集合装载到List集合中
List<Map<String,Object>> list = jt.queryForList(sql);
Iterator<Map<String, Object>> it = list.iterator();
while(it.hasNext())
{
System.out.println(it.next());//结果把所有的行全部查询出来
}
}
/**
* 6. 查询所有记录,将其封装为Emp对象(既JavaBean)的List集合
*/
@Test
public void test6()
{
String sql = "select * from emp";
//query的参数:RowMapper接口对象,我们既可以使用默认的RowMapper接口的实现类,也可以自定义自己的实现类
//这里我们先定义自己的实现类。使用匿名内部类,RowMapper的泛型定义为我们想要封装的Emp类
//同样这个RowMapper类由JDBCTemplate提供,java文档查询不到
List<Emp> list = jt.query(sql , new RowMapper<Emp>(){
//重写RowMapper的mapRow方法
//这个方法会通过JDBCTemplate自动获取对应连接的结果集对象
@Override
public Emp mapRow(ResultSet resultSet, int i) throws SQLException
{
//为了获取Emp类的对象,我们每查询表的一行就会为Emp的各个属性赋值,并将赋值好的Emp对象返回List集合
//这个方法query会根据SQL查询所有的行,并且将所有的行都封装为Emp对象,我们不需要使用ResultSet的next方法执行循环来获取每一行
//创建Emp对象,我们每获取一行就会调用一次mapRow,封装一个Emp对象
Emp emp = new Emp();
//先通过结果集ResultSet的对象resultSet获取每一列对应的值
int id = resultSet.getInt(1);
String ename = resultSet.getString("ename");
int job_id = resultSet.getInt(3);
int mgr = resultSet.getInt(4);
Date joindate = resultSet.getDate(5);
double salary = resultSet.getDouble(6);
double bonus = resultSet.getDouble(7);
int dept_id = resultSet.getInt(8);
//接下来将从表中获取的每一列的值赋予Emp的成员属性
emp.setId(id);
emp.setEname(ename);
emp.setJob_id(job_id);
emp.setMgr(mgr);
emp.setJoindate(joindate);
emp.setSalary(salary);
emp.setBonus(bonus);
emp.setDept_id(dept_id);
//接下来将封装好的Emp对象返回
return emp;
}
});
//将表中每一行的数据封装为一个Emp对象并封装到LIst集合中,打印这些emp对象
for (Emp emp : list)
{
System.out.println(emp);
}
}
/**
* 6. 查询所有记录,将其封装为Emp对象的List集合
*/
@Test
public void test6_2()
{
//这里我们使用默认的RowMapper接口的实现类BeanPropertyRowMapper,这样我们就省去中间很多步骤,很方便
//new BeanPropertyRowMapper<类型>(类型.class)
//2、定义SQL语句
String sql = "select * from emp";
//3、使用JDBCTemplate对象执行查询,并返回装载Emp对象的List集合
List<Emp> list = jt.query(sql, new BeanPropertyRowMapper<Emp>(Emp.class));//参数是定义的Emp类的.class对象
for (Emp emp : list)
{
System.out.println(emp);
}
/*
之前我们执行查询需要
1、获取数据库连接;2、定义SQL查询语句;3、获取数据库执行对象PreparedStatement;
4、为SQL语句的占位符赋值;5、执行SQL并获取结果集ResultSet;6、通过结果集ResultSet的获取方法可以获取各个参数(加入循环可以获取所有行的参数)
这里使用JDBCTemplate只需要
1、获取JDBCTemplate对象,参数是数据库连接池对象DataSource(可以通过Druid的工具类JDBCUtil获取)
2、定义SQL查询语句
3、根据查询的表,设置一个与表相关的类,如Emp
4、使用JDBCTemplate的query方法,执行查询,并使用BeanPropertyRowMapper将与表相关的类Emp的各个属性赋值,并将赋值好的Emp对象
封装到List<Emp>集合
5、直接查询List集合的Emp对象就可以看到整个表
*/
}
/**
* 7. 查询总记录数
*/
@Test
public void test7()
{
String sql = "select count(id) from emp";
//queryForObject方法第二个参数是查询结果类型的Class对象。
//比如这里查询结果是有多少个id,long类型,我们将参数设置为Long.class.
//当然设置为Integer类型也可以
Integer total = jt.queryForObject(sql, Integer.class);
System.out.println(total);//14
}
}