之前我们学习了JavaSE,编写了Java程序,数据保存在变量、数组、集合等中,无法持久化,后来学习了IO流可以将数据写入文件,但不方便管理数据以及维护数据的关系;
后来我们学习了数据库管理软件MySQL,可以方便的管理数据。
那么如何将它俩结合起来呢?即Java程序<==>MySQL,实现数据的存储和处理。
那么就可以使用JDBC技术。
JDBC:Java Database Connectivity,它是代表一组独立于任何数据库管理系统(DBMS)的API,声明在java.sql与javax.sql包中,是SUN(现在Oracle)提供的一组接口规范。由各个数据库厂商来提供实现类,这些实现类的集合构成了数据库驱动jar。
即JDBC技术包含两个部分:
(1)java.sql包和javax.sql包中的API
因为为了项目代码的可移植性,可维护性,SUN公司从最初就制定了Java程序连接各种数据库的统一接口规范。这样的话,不管是连接哪一种DBMS软件,Java代码可以保持一致性。
(2)各个数据库厂商提供的jar
因为各个数据库厂商的DBMS软件各有不同,那么内部如何通过sql实现增、删、改、查等管理数据,只有这个数据库厂商自己更清楚,因此把接口规范的实现交给各个数据库厂商自己实现。
代码编写步骤:
JDBC访问数据库步骤
1:注册一个Driver驱动
```
三部曲:
(1)将DBMS数据库管理软件的驱动jar拷贝到项目的libs目录中
例如:mysql-connector-java-5.1.36-bin.jar
(2)把驱动jar添加到项目的build path中。(IDEA中右键点击驱动jar包》点击add as library)
(3)将驱动类(DriverManager)加载到内存中,DriverManager可以用于创建命令发送器。来执行sql语句
DriverManager.registerDriver(new Driver()); # 方式1
Class.forName("com.mysql.cj.jdbc.Driver"); # 方式2。不同版本的mysql驱动包Driver类路径可能不同
```
两种加载驱动类DriverManager的方式:
DriverManager.registerDriver(new Driver());
:注册一个Driver实例Class.forName("com.mysql.cj.jdbc.Driver")
:初始化Driver类。package com.mysql.cj.jdbc;
import java.sql.DriverManager;
import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
// 本质上是初始化Driver类时,会执行该静态代码块,完成Driver实例对象的创建。跟方式1一样
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
2:创建数据库连接(Connection)
Connection conn = DriverManager.getConnection(url,username,password);
// mysql的url:jdbc:mysql://ip:端口/数据库名?参数名=参数值
// 例如:jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=utf8
(如果JDBC程序与服务器端的字符集不一致,会导致乱码,那么可以通过参数指定服务器端的字符集)
3:创建SQL命令发送器Statement
// 创建Statement或PreparedStatement对象
// 创建statement对象
Connection connection = DriverManager.getConnection(url, "root", "xxx");
Statement statement = connection.createStatement();
4:通过Statement发送SQL命令并得到结果
// 注意:sql语句中的字符串只能用单引号括起来,因为外层是java中的字符串必须使用双引号,不能冲突
// executeUpdate用于提交增删改操作的sql,返回int数据。大于0表示执行成功
int res = statement.executeUpdate("insert into employ3 values(41,'西门庆','男',14000.22,1)");
// executeQuery用于执行查询操作的sql,返回ResultSet 对象。其中储存了sql查询的结果
ResultSet resultSet = statement.executeQuery("select * from employ3");
5:处理结果(select语句需要)
6:关闭数据库资源ResultSet Statement Connection
statement.close(); // 关闭命令发射器
connection.close(); // 关闭连接
相关的API:
1、DriverManager:驱动管理类
2、Connection:代表数据库连接
3、Statement和PreparedStatement:用来执行sql
执行增、删、改:int executeUpate()
执行查询:ResultSet executeQuery()
4、如何遍历ResultSet ?
(0)ResultSet可以使用迭代来查询
(1)boolean next():判断是否还有下一行
(2)getString(字段名或序号):获取字符串类型字段的数据内容。注意如果使用序号,mysql中是以1开始的
(3)getInt(字段名或序号):获取整数类型字段的数据内容。注意如果使用序号,mysql中是以1开始的
(4)getObject(字段名或序号):获取任意类型字段的数据内容。注意如果使用序号,mysql中是以1开始的
示例代码1:增、删、改
public class TestJDBC {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//1、注册驱动
// (1)方式一:Class.forName("驱动类的全名称")
Class.forName("com.mysql.jdbc.Driver");
// (2)创建驱动类的对象
// new com.mysql.jdbc.Driver();//硬编码
//(3)通过DriverManager注册驱动
// DriverManager.registerDriver(new com.mysql.jdbc.Driver());//硬编码
//2、获取连接,连接数据库
//TCP/IP协议编程,需要服务器的IP地址和端口号
//mysql的url格式:jdbc协议:子协议://主机名:端口号/要连接的数据库名
String url = "jdbc:mysql://localhost:3306/test";//其中test是数据库名
String user = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url, user, password);
//3、执行sql
//添加一个部门到数据库的t_department表中
//(1)编写sql
String sql = "insert into t_department values(null,'计算部2','计算钞票2')";
/*
* 回忆: TCP/IP程序时
* Socket代表连接
* socket.getOutputStream()来发送数据,
* socket.getInputStream()来接收数据
*
* 可以把Connection比喻成Socket
* 把Statement比喻成OutputStream
*/
//(2)获取Statement对象
Statement st = conn.createStatement();
//(3)执行sql
int len = st.executeUpdate(sql);
//(4)处理结果
System.out.println(len>0?"成功":"失败");
//4、关闭
st.close();
conn.close();
}
}
示例代码2:查询
public class TestSelect {
public static void main(String[] args) throws Exception{
// 1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2、连接数据库
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
// 3、执行sql
String sql = "SELECT * FROM t_department";
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery(sql);//ResultSet看成InputStream
// 4、处理结果
while(rs.next()){//next()表示是否还有下一行
Object did = rs.getObject(1);//获取第n列的值
Object dname = rs.getObject(2);
Object desc = rs.getObject(3);
/*
int did = rs.getInt("did");//也可以根据列名称,并且可以按照数据类型获取
String dname = rs.getString("dname");
String desc = rs.getString("description");
*/
System.out.println(did +"\t" + dname + "\t"+ desc);
}
// 5、关闭
rs.close();
st.close();
conn.close();
}
}
案例:我们希望键盘输入用户名,密码保存到sql的用户表中
class Demo2{
public static void main(String[] args) throws ClassNotFoundException, SQLException {
// 创建驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 创建连接
String url = "jdbc:mysql://localhost:3306/test2";
Connection connection = DriverManager.getConnection(url, "root", "xxx");
// 创建命令发射器
Statement statement = connection.createStatement();
// 准备sql
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名:");
String user = scanner.nextLine();
System.out.println("请输入密码:");
int password = scanner.nextInt();
// 这种拼接是错误的,因为user和password是sql中的字符串,本身就需要引号括起来
// String sql = "insert into user(user,password) values(" + user + "," + password + ")"; // 等价于"insert into user(user,password) values(张三,123456)"是错误的sql语句
// 等价于 insert into user(user,password) values(' + 张三 + ',' + 123456 + ')
// 拼接的sql是:insert into user(user,password) values('张三','123456') 是正确的sql
String sql = "insert into user(user,password) values('" + user + "','" + password + "')";
// 执行sql
int res = statement.executeUpdate(sql);
// 获取结果
System.out.println(res>0?"注册成功":"注册失败");
// 关闭资源
scanner.close();
statement.close();
connection.close();
}
}
可以发现,statement要动态拼接sql非常麻烦。
案例:键盘输入用户名密码,然后查询user表是否存在该用户名密码,如果存在则提示登录成功,否则登录失败
class Demo3{
public static void main(String[] args) throws ClassNotFoundException, SQLException {
// 创建驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 创建连接
String url = "jdbc:mysql://localhost:3306/test2";
Connection connection = DriverManager.getConnection(url, "root", "xxx");
// 创建命令发射器
Statement statement = connection.createStatement();
// 准备sql
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名:");
String user = scanner.nextLine();
System.out.println("请输入密码:");
String password = scanner.nextLine();
String sql = "select * from user where (user='"+user+"'and password='"+password+"')";
System.out.println("sql = " + sql);
// 执行sql
ResultSet res = statement.executeQuery(sql);
// 获取结果
String resUser = null;
String resPassword = null;
while (res.next()){
resUser = res.getString("user");
resPassword = res.getString("password");
}
if (resUser != null && resPassword != null){
System.out.println("登录成功");
}else {
System.out.println("登陆失败");
}
// 关闭资源
scanner.close();
statement.close();
connection.close();
}
}
然后我们来运行代码,实现sql注入:
请输入用户名:
张三
请输入密码:
123' or '1'='1
sql = select * from user where (user='张三'and password='123' or '1'='1')
登录成功
可以看到我们使用sql注入,即使输入错误的密码也登录成功了。
(1)sql拼接
String sql = "insert into t_employee(ename,tel,gender,salary) values('" + ename + "','" + tel + "','" + gender + "'," + salary +")";
Statement st = conn.createStatement();
int len = st.executeUpdate(sql);
(2)sql注入
String sql = "SELECT * FROM t_employee where ename='" + ename + "'";
//如果我此时从键盘输入ename值的时候,输入:张三' or '1'= '1
//结果会把所有数据都查询出来
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery(sql);
(3)无法处理blob等类型的数据(图片,文件等)
创建含有Blob字段类型的表 测试插入
String sql = "insert into user(username,photo) values('zs', 图片字节流)";
//此时photo是blob类型的数据时,无法在sql中直接拼接
PreparedStatement解决问题:
class Demo2{
public static void main(String[] args) throws ClassNotFoundException, SQLException {
// 创建驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 创建连接
String url = "jdbc:mysql://localhost:3306/test2";
Connection connection = DriverManager.getConnection(url, "root", "xxx");
// 准备sql
String sql = "insert into user(user,password) values(?,?)"; //使用?来占用一个位置
// 创建命令发射器
PreparedStatement statement = connection.prepareStatement(sql);// 在创建发射器时,要传带?的sql,这样mysql端就会对这个sql进行预编译
// 填充sql
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名:");
String user = scanner.nextLine();
System.out.println("请输入密码:");
int password = scanner.nextInt();
statement.setObject(1,user); // 给第一个?赋值
statement.setObject(2,password); // 给第2个?赋值
// 执行sql
int res = statement.executeUpdate(); // 因为sql在创建发射器时已经编译完成,不需要再次传入sql
// 获取结果
System.out.println(res>0?"注册成功":"注册失败");
// 关闭资源
scanner.close();
statement.close();
connection.close();
}
}
class Demo3 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
// 创建驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 创建连接
String url = "jdbc:mysql://localhost:3306/test2";
Connection connection = DriverManager.getConnection(url, "root", "xxx");
// 准备sql
String sql = "select * from user where (user=? and password=?)";
// 创建命令发射器
PreparedStatement statement = connection.prepareStatement(sql);
// 填充sql
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名:");
String user = scanner.nextLine();
System.out.println("请输入密码:");
String password = scanner.nextLine();
statement.setObject(1,user);
statement.setObject(2,password);
// 执行sql
ResultSet res = statement.executeQuery();
// 获取结果
String resUser = null;
String resPassword = null;
while (res.next()) {
resUser = res.getString("user");
resPassword = res.getString("password");
}
if (resUser != null && resPassword != null) {
System.out.println("登录成功");
} else {
System.out.println("登陆失败");
}
// 关闭资源
scanner.close();
statement.close();
connection.close();
}
}
运行一下代码。看看sql注入能否成功:
请输入用户名:
张三
请输入密码:
123 or 1==1 # 注入失败了,因为现在会将输入的所有内容作为密码进行校验
登陆失败
String sql = "insert into user(username,photo) values(?,?)";
PreparedStatement pst = conn.prepareStatement(sql);
//设置?的值
pst.setObject(1, "zs");
FileInputStream fis = new FileInputStream("D:/QMDownload/img/美女/15.jpg");
pst.setBlob(2, fis);
int len = pst.executeUpdate();
System.out.println(len>0?"成功":"失败");
注意两个问题:
①my.ini关于上传的字节流文件有大小限制,可以在my.ini中配置变量
max_allowed_packet=16M
②mysql中使用tinyblob、blob、mediumblob、longblob四种数据类型来以二进制的形式储存图片,文件等数据。
③mysql中每一种blob有各自大小限制:
tinyblob:255字节、blob:65k、mediumblob:16M、longblob:4G
练习:
新建一个img表,向表中储存图片,读取图片
①:新建img表
create table img(
id int primary key auto_increment,
img mediumblob,
iname char(20)
);
②:向表中插入数据
class Demo2 {
public static void main(String[] args) throws ClassNotFoundException, SQLException, FileNotFoundException {
// 创建驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 创建连接
String url = "jdbc:mysql://localhost:3306/test2";
Connection connection = DriverManager.getConnection(url, "root", "xxx");
// 准备sql
String sql = "insert into img(img,iname) values(?,?),(?,?)"; //使用?来占用一个位置
// 创建命令发射器
PreparedStatement statement = connection.prepareStatement(sql);// 在创建发射器时,要传带?的sql,这样mysql端就会对这个sql进行预编译
// 填充sql
FileInputStream file1 = new FileInputStream("E:\\hehe\\0K0xGPu9gU.jpeg");
FileInputStream file2 = new FileInputStream("E:\\hehe\\4c024fcb172f8e3c4614747deff0b80e.jpg");
statement.setObject(1, file1); // 给第一个?赋值 // blob类型的数据,要求传入一个InputStream类型的数据
statement.setObject(2, "img1"); // 给第2个?赋值
statement.setObject(3, file2); // 给第3个?赋值 // blob类型的数据,要求传入一个InputStream类型的数据
statement.setObject(4, "img2"); // 给第4个?赋值
// 执行sql
int res = statement.executeUpdate(); // 因为sql在创建发射器时已经编译完成,不需要再次传入sql
// 获取结果
System.out.println(res > 0 ? "保存成功" : "保存失败");
// 关闭资源
statement.close();
connection.close();
}
}
class Demo3 {
public static void main(String[] args) throws ClassNotFoundException, SQLException, IOException {
// 创建驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 创建连接
String url = "jdbc:mysql://localhost:3306/test2";
Connection connection = DriverManager.getConnection(url, "root", "xxx");
// 准备sql
String sql = "select * from img where (id=1)";
// 创建命令发射器
PreparedStatement statement = connection.prepareStatement(sql);
// 执行sql
ResultSet res = statement.executeQuery();
// 获取结果
String resUser = null;
String resPassword = null;
while (res.next()) {
Blob img = res.getBlob("img"); // 返回的是一个blob对象
// 将读取到的blob数据内容保存到文件中
InputStream binaryStream = img.getBinaryStream();// 将blob对象转化为InputStream对象
byte[] bytes = new byte[8];
int len;
FileOutputStream fileOutputStream = new FileOutputStream("D:ss.jpg");
while ((len = binaryStream.read(bytes)) != -1) {
fileOutputStream.write(bytes,0,len);
}
}
// 关闭资源
statement.close();
connection.close();
}
}
运行代码,图片保存成功了:
实际开发中,我们往往将id字段设置为主键并自动增长,那么当我们update一条数据时,获取到它的id就非常关键
/*
* 我们通过JDBC往数据库的表格中添加一条记录,其中有一个字段是自增的,那么在JDBC这边怎么在添加之后直接获取到这个自增的值
* PreparedStatement是Statement的子接口。
* Statement接口中有一些常量值:
* (1)Statement.RETURN_GENERATED_KEYS
*
* 要先添加后获取到自增的key值:
* (1)PreparedStatement pst = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
* (2)添加sql执行完成后,通过PreparedStatement的对象调用getGeneratedKeys()方法来获取自增长键值,遍历结果集
* ResultSet rs = pst.getGeneratedKeys();
*/
public class TestAutoIncrement {
public static void main(String[] args) throws Exception{
//1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2、获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
//3、执行sql
String sql = "insert into t_department values(null,?,?)";
/*
* 这里在创建PreparedStatement对象时,传入第二个参数的作用,就是告知服务器端
* 当执行完sql后,把自增的key值返回来。
*/
PreparedStatement pst = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
//设置?的值
pst.setObject(1, "测试部");
pst.setObject(2, "测试项目数据");
//执行sql
int len = pst.executeUpdate();//返回影响的记录数
if(len>0){
//从pst中获取到服务器端返回的键值
ResultSet rs = pst.getGeneratedKeys();
//因为这里的key值可能多个,因为insert语句可以同时添加多行,所以用ResultSet封装
//这里因为只添加一条,所以用if判断
if(rs.next()){
Object key = rs.getObject(1); // 这里用1去获取自增值,不要跟查询语句返回的结果集搞混了
System.out.println("自增的key值did =" + key);
}
}
//4、关闭
pst.close();
conn.close();
}
}
需要在url的末尾添加此参数:rewriteBatchedStatements=true
/*
* 批处理:
* 批量处理sql
*
* 例如:
* (1)订单明细表的多条记录的添加
* (2)批量添加模拟数据
* ...
*
* 不用批处理,和用批处理有什么不同?
* 批处理的效率很多
*
* 如何进行批处理操作?
* (1)在url中要加一个参数
* rewriteBatchedStatements=true
* 那么我们的url就变成了 jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true
* 这里的?,表示?后面是客户端给服务器端传的参数,多个参数直接使用&分割
* (2)调用方法不同
* pst.addBatch();
* int[] all = pst.executeBatch();
*
* 注意:如果批量添加时,insert使用values,不要使用value
*/
public class TestBatch {
public static void main(String[] args) throws Exception{
long start = System.currentTimeMillis();
//例如:在部门表t_department中添加1000条模拟数据
//1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2、获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true", "root", "123456");
//3、执行sql
String sql = "insert into t_department values(null,?,?)";
PreparedStatement pst = conn.prepareStatement(sql);
//设置?的值
for (int i = 1; i <=1000; i++) {
pst.setObject(1, "模拟部门"+i);
pst.setObject(2, "模拟部门的简介"+i);
pst.addBatch();//添加到批处理一组操作中,攒一块处理
/* if(i % 500 == 0){//有时候也攒一部分,执行一部分
//2.执行
pst.executeBatch();
//3.清空
pst.clearBatch();
}*/
}
pst.executeBatch(); // 执行批处理
//4、关闭
pst.close();
conn.close();
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start));//耗时:821
}
}
/*
* mysql默认每一个连接是自动提交事务的。
* 那么当我们在JDBC这段,如果有多条语句想要组成一个事务一起执行的话,那么在JDBC这边怎么设置手动提交事务呢?
* (1)在执行之前,设置手动提交事务
* Connection的对象.setAutoCommit(false)
* (2)成功:
* Connection的对象.commit();
* 失败:
* Connection的对象.rollback();
*
* 补充说明:
* 为了大家养成要的习惯,在关闭Connection的对象之前,把连接对象设置回自动提交
* (3)Connection的对象.setAutoCommit(true)
*
* 因为我们现在的连接是建立新的连接,那么如果没有还原为自动提交,没有影响。
* 但是我们后面实际开发中,每次获取的连接,不一定是新的连接,而是从连接池中获取的旧的连接,而且你关闭也不是真关闭,而是还给连接池,供别人接着用。以防别人拿到后,以为是自动提交的,而没有commit,最终数据没有成功。
*/
public class TestTransaction {
public static void main(String[] args) throws Exception{
/*
* 一般涉及到事务处理的话,那么业务逻辑都会比较复杂。
* 例如:购物车结算时:
* (1)在订单表中添加一条记录
* (2)在订单明细表中添加多条订单明细的记录(表示该订单买了什么东西)
* (3)修改商品表的销量和库存量
* ...
* 那么我们今天为了大家关注事务的操作,而不会因为复杂的业务逻辑的影响导致我们的理解,那么我们这里故意
* 用两条修改语句来模拟组成一个简单的事务。
* update t_department set description = 'xx' where did = 2;
* update t_department set description = 'yy' where did = 3;
*
* 我希望这两天语句要么一起成功,要么一起回滚
* 为了制造失败,我故意把第二条语句写错
* update t_department set description = 'yy' (少了where) did = 3;
*/
//1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2、获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
//设置手动提交事务
conn.setAutoCommit(false);
//3、执行sql
String sql1 = "update t_department set description = 'xx' where did = 2";
String sql2 = "update t_department set description = 'yy' did = 3";//这是错的
//使用prepareStatement的sql也可以不带?
PreparedStatement pst = null;
try {
pst = conn.prepareStatement(sql1);
int len = pst.executeUpdate();
System.out.println("第一条:" + (len>0?"成功":"失败"));
pst = conn.prepareStatement(sql2);
len = pst.executeUpdate();
System.out.println("第二条:" + (len>0?"成功":"失败"));
//都成功了,就提交事务
System.out.println("提交");
conn.commit();
} catch (Exception e) {
System.out.println("回滚");
//失败要回滚
conn.rollback();
}
//4、关闭
pst.close();
conn.setAutoCommit(true);//还原为自动提交
conn.close();
}
}
像前面代码那样将数据库的url、用户名、密码等直接硬编码到代码中是不合适的,我们可以使用配置文件来储存。
properties是一种配置文件,以key
=value
的形式来存放数据。
现在我们在模块的src目录下去创建properties对象,用来储存我们的数据库配置信息
Properties
类是Hashtable
的子类,也是一种集合,以[key,value]的形式储存数据。public synchronized Object setProperty(String key, String value):将【key,value】储存到Properties集合中
public String getProperty(String key):通过key获取value的值
public void store(OutputStream out, String comments):将集合中的数据持久化保存到文件中,comments是对该文件的描述
public synchronized void load(InputStream inStream):从文件中的数据读取到集合中
// 向集合中添加读取数据
class Demo6{
public static void main(String[] args) {
Properties properties = new Properties();
properties.setProperty("1", "哈哈"); // 储存数据到集合中
properties.setProperty("2", "嘿嘿");
properties.setProperty("3", "嘻嘻");
String res = properties.getProperty("1");// 获取"1"对应的value值
System.out.println("res = " + res);
}
}
// 将集合中的数据保存到文件中
class Demo4{
public static void main(String[] args) throws IOException {
Properties properties = new Properties();
properties.setProperty("1", "哈哈");
properties.setProperty("2", "嘿嘿");
properties.setProperty("3", "嘻嘻");
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream("E:\\Java全栈\\code\\JavaSE\\src\\jdbc.properties"),"UTF-8");
properties.store(outputStreamWriter,"this is a properties file"); // 将集合中的数据保存到文件jdbc.properties中
}
}
class Demo5{
// 读取文件中的数据到集合中
public static void main(String[] args) throws IOException {
Properties properties = new Properties();
InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream("E:\\Java全栈\\code\\JavaSE\\src\\jdbc.properties"), "UTF-8");
properties.load(inputStreamReader); // 读取文件中的数据到集合中
System.out.println("properties.getProperty(\"1\") = " + properties.getProperty("1"));
}
}
package com.oy.jdbctest.jdbcutils;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
public class MyJdbcUtils{
private static String user;
private static String password;
private static String url;
private static String driver;
// 读取配置文件的内容,最好放到静态代码块中。这样只会在初始化类时,加载一次
// 而不会每生成一个实例化对象都调用一次,减少IO操作
static {
try {
Properties properties = new Properties();
//properties.load(new FileInputStream("E:\\Java全栈\\code\\JavaSE\\src\\jdbc.properties")); // 使用配置文件的绝对路径
properties.load(JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties")); // 使用反射来使用配置文件的相对路径
user = properties.getProperty("user");
password = properties.getProperty("password");
url = properties.getProperty("url");
driver = properties.getProperty("driver");
System.out.println(url+"\t"+user+"\t"+password+"\t"+driver);
} catch (IOException e) {
e.printStackTrace();
}
}
}
我们可以将数据库连接封装为一个方法。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
public class MyJdbcUtils{
private static String user;
private static String password;
private static String url;
private static String driver;
// 读取配置文件的内容,最好放到静态代码块中。这样只会在初始化类时,加载一次
// 而不会每生成一个实例化对象都调用一次,减少IO操作
static {
try {
Properties properties = new Properties();
//properties.load(new FileInputStream("E:\\Java全栈\\code\\JavaSE\\src\\jdbc.properties")); // 使用配置文件的绝对路径
properties.load(JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties")); // 使用反射来使用配置文件的相对路径
user = properties.getProperty("user");
password = properties.getProperty("password");
url = properties.getProperty("url");
driver = properties.getProperty("driver");
System.out.println(url+"\t"+user+"\t"+password+"\t"+driver);
} catch (IOException e) {
e.printStackTrace();
}
}
// 连接数据库
public Connection getConnect(){
try {
// 创建驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 创建连接
String url = "jdbc:mysql://localhost:3306/test2";
Connection connection = DriverManager.getConnection(url, "root", "xxx");
return connection;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return null;
}
}
连接数据库后,会有一大堆东西需要关闭。我们也可以给他包装成一个方法
package com.oy.jdbctest.jdbcutils;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
public class MyJdbcUtils{
private static String user;
private static String password;
private static String url;
private static String driver;
// 读取配置文件的内容,最好放到静态代码块中。这样只会在初始化类时,加载一次
// 而不会每生成一个实例化对象都调用一次,减少IO操作
static {
try {
Properties properties = new Properties();
//properties.load(new FileInputStream("E:\\Java全栈\\code\\JavaSE\\src\\jdbc.properties")); // 使用配置文件的绝对路径
properties.load(JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties")); // 使用反射来使用配置文件的相对路径
user = properties.getProperty("user");
password = properties.getProperty("password");
url = properties.getProperty("url");
driver = properties.getProperty("driver");
System.out.println(url+"\t"+user+"\t"+password+"\t"+driver);
} catch (IOException e) {
e.printStackTrace();
}
}
// 连接数据库
public static Connection getConnect(){
try {
// 创建驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 创建连接
String url = "jdbc:mysql://localhost:3306/test2";
Connection connection = DriverManager.getConnection(url, "root", "xxx");
return connection;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return null;
}
// 关闭资源
public static void closeConnect(Connection connection, Statement statement, ResultSet resultSet){
if (connection != null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement!=null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
class Demo7{
public static void main(String[] args) throws SQLException {
// 获取连接
Connection connect = MyJdbcUtils.getConnect();
// 生成sql命令发射器
PreparedStatement preparedStatement = connect.prepareStatement("select * from employ3");
// 执行sql
ResultSet resultSet = preparedStatement.executeQuery();
// 获取结果
while (resultSet.next()){
String name = resultSet.getString("ename");
String salary = resultSet.getString("salary");
System.out.println("姓名:"+name+"\t薪资:"+salary);
}
// 如果每这么多资源需要关闭,直接填null就行
JdbcUtils.closeConnect(connect,preparedStatement,resultSet);
}
}
实际开发者,一张数据表往往对应Java中的一个类,表字段对应类的成员属性。所有我们获取到数据表中的数据时,需要将它们封装为该类的一个对象。
如下是一张accout表,我们获取到表中的数据后将每一条记录封装为一个Account对象。
①:创建Account类
package com.oy.jdbctest.accouttest;
public class Account {
private int id;
private String aname;
private double money;
public Account(int id, String aname, double money) {
this.id = id;
this.aname = aname;
this.money = money;
}
public Account() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getAname() {
return aname;
}
public void setAname(String aname) {
this.aname = aname;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", aname='" + aname + '\'' +
", money=" + money +
'}';
}
}
②:获取数据表account中的所有数据,并封装为Account类对象
package com.oy.jdbctest.accouttest;
import com.oy.jdbctest.jdbcutils.JdbcUtils;
import java.lang.reflect.Field;
import java.sql.*;
class Test {
public static void main(String[] args) {
try {
// 1.获取连接
Connection connect = MyJdbcUtils.getConnect();
// 2.生成发射器
PreparedStatement pst = connect.prepareStatement("select * from acount");
// 3.执行sql
ResultSet resultSet = pst.executeQuery();
// 4.处理结果,将读取的数据封装为对象
// getMetaData()方法可以获取结果集resultSet的元数据
// 数据就是描述结果集中的数据的数据,例如:列数,列名称, 列别名等
/*
* 元数据.getColumnCount(); 获取列数
* 元数据.getColumnLabel(n); 获取第n列字段的别名,从1开始计算
* 元数据.getColumnClassName(n); 获取第n列字段的字段名(别名不起作用)
*/
ResultSetMetaData metaData = resultSet.getMetaData();
int len = metaData.getColumnCount();
while (resultSet.next()){
Account account = new Account(); // 每一条记录创建一个对象
Class<? extends Account> aClass = account.getClass(); // 获取class对象,使用反射给属性赋值
// 遍历所有字段,并将值赋给对象的属性
for (int i = 1; i <= len; i++) {
String key = metaData.getColumnLabel(i); // 获取字段
Object value = resultSet.getObject(key); // 获取该条记录字段的值
Field declaredField = aClass.getDeclaredField(key); // 获取属性对象
declaredField.setAccessible(true); // 将私有属性设置为可访问
declaredField.set(account,value); // 给属性赋值
}
System.out.println(account); // 打印一下对象
}
} catch (SQLException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
前面我们将数据库的连接,释放操作进行了封装。但是还不够,我们可以将数据库的增删改查操作进行封装,这样我们在想要操作数据库时,只需要使用相关方法即可,不需要去关心数据库的连接,释放操作。
package com.oy.jdbctest.jdbcutils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class BasiDao1 {
/**
* /通用的增删改操作
* @param sql: 需要执行的sql
* @param args:需要传入的参数
* @return int: 本次操作影响的记录数
*/
public int basUpdate(String sql,Object... args) throws SQLException {
// 1.获取连接
Connection connect = MyJdbcUtils.getConnect();
// 2.创建命令发射器
PreparedStatement preparedStatement = connect.prepareStatement(sql);
// 3.填充sql
for (int i = 1; i < args.length+1; i++) {
//设置?的值
//因为不知道sql中是否有?,以及?设置为什么值,通过形参来传入
//Object... args可变形参,可以传入0~n个值
//如果没有传入,说明没有?需要设置
//如果传入了n个值,那么说明sql中有n个?需要设置
// sql中的?是从1开始,而args的下标是从0开始
preparedStatement.setObject(i,args[i-1]);
}
// 4.执行sql
int i = preparedStatement.executeUpdate();
return i;
}
}
测试一下:
class Demo10 {
public static void main(String[] args) {
try {
BasiDao1 basiDao1 = new BasiDao1();
int res = basiDao1.basUpdate("insert into user values(null,?,?)", "王麻子", "123456");
System.out.println(res > 0 ? "添加成功" : "添加失败");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
查询的方法会返回一个结果集,需要将结果集中的数据封装为对应的Java类对象。前面我们已经讲过通过元数据和反射来实现,但是现在我们要实现通用的查询方法,也就是改方法来能适用于任何的类(满足JavaBean)。那么我们需要使用泛型来实现
package com.oy.jdbctest.jdbcutils;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
public class BasiDao1 {
/**
* /通用的增删改操作
* @param sql: 需要执行的sql
* @param args:需要传入的参数
* @return int: 本次操作影响的记录数
*/
public int basUpdate(String sql,Object... args) throws SQLException {
// 1.获取连接
Connection connect = MyJdbcUtils.getConnect();
// 2.创建命令发射器
PreparedStatement preparedStatement = connect.prepareStatement(sql);
// 3.填充sql
for (int i = 1; i < args.length+1; i++) {
//设置?的值
//因为不知道sql中是否有?,以及?设置为什么值,通过形参来传入
//Object... args可变形参,可以传入0~n个值
//如果没有传入,说明没有?需要设置
//如果传入了n个值,那么说明sql中有n个?需要设置
// sql中的?是从1开始,而args的下标是从0开始
preparedStatement.setObject(i,args[i-1]);
}
// 4.执行sql
int i = preparedStatement.executeUpdate();
// 关闭资源
MyJdbcUtils.closeConnect(connect,preparedStatement,null);
return i;
}
/**
* 通用的查询方法,适用于多个JavaBean类对象
* @param clazz:数据表对应的Java类的class对象
* @param sql:需要执行的sql语句
* @param args:需要传入的参数
* @param :对象类型
* @return Arraylist: 对象列表
*/
public <T> ArrayList<T> getAll(Class<T> clazz,String sql, Object... args) throws Exception {
// 1.创建连接
Connection connect = MyJdbcUtils.getConnect();
// 2.生成命令发射器
PreparedStatement pst = connect.prepareStatement(sql);
// 3.填充sql
for (int i = 1; i < args.length+1; i++) {
//设置?的值
//因为不知道sql中是否有?,以及?设置为什么值,通过形参来传入
//Object... args可变形参,可以传入0~n个值
//如果没有传入,说明没有?需要设置
//如果传入了n个值,那么说明sql中有n个?需要设置
// sql中的?是从1开始,而args的下标是从0开始
pst.setObject(i,args[i-1]);
}
// 4.执行sql
ResultSet resultSet = pst.executeQuery();
// 5.处理结果,将其封装为对应的对象
ResultSetMetaData metaData = resultSet.getMetaData(); //获取结果集中的元数据
int count = metaData.getColumnCount(); // 获取字段列数
ArrayList<T> lists = new ArrayList<>(); // 生成一个列表,用于存放对象
while (resultSet.next()){ // 获取到结果中的每一行记录
T t = clazz.newInstance(); // 获取JavaBean的实例对象
for (int i = 0; i < count; i++) {
String columnLabel = metaData.getColumnLabel(i + 1); // 获取字段名
Object value = resultSet.getObject(columnLabel); // 获取记录中对应字段的数据值
// 使用反射将数据值保存JavaBean实例对象的属性中
Field declaredField = clazz.getDeclaredField(columnLabel);
declaredField.setAccessible(true);
declaredField.set(t,value);
}
lists.add(t); // 将对象添加到集合中
}
MyJdbcUtils.closeConnect(connect,pst,resultSet);
return lists;
}
}
测试一下:
class Demo11{
public static void main(String[] args) throws Exception {
BasiDao1 basiDao1 = new BasiDao1();
ArrayList lists = basiDao1.getAll(Account.class,"select * from acount");
lists.forEach(System.out::println);
}
}
效果如下:
Account{id=1, aname='张三', money=2500.0}
Account{id=2, aname='李四', money=1500.0}
package com.oy.jdbctest.jdbcutils;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class BasiDao1 {
/**
* /通用的增删改操作
* @param sql: 需要执行的sql
* @param args:需要传入的参数
* @return int: 本次操作影响的记录数
*/
public int basUpdate(String sql,Object... args) throws SQLException {
// 1.获取连接
Connection connect = MyJdbcUtils.getConnect();
// 2.创建命令发射器
PreparedStatement preparedStatement = connect.prepareStatement(sql);
// 3.填充sql
for (int i = 1; i < args.length+1; i++) {
//设置?的值
//因为不知道sql中是否有?,以及?设置为什么值,通过形参来传入
//Object... args可变形参,可以传入0~n个值
//如果没有传入,说明没有?需要设置
//如果传入了n个值,那么说明sql中有n个?需要设置
// sql中的?是从1开始,而args的下标是从0开始
preparedStatement.setObject(i,args[i-1]);
}
// 4.执行sql
int i = preparedStatement.executeUpdate();
// 关闭资源
MyJdbcUtils.closeConnect(connect,preparedStatement,null);
return i;
}
/**
* 通用的查询方法,适用于多个JavaBean类对象
* @param clazz:数据表对应的Java类的class对象
* @param sql:需要执行的sql语句
* @param args:需要传入的参数
* @param :对象类型
* @return Arraylist: 对象列表
*/
public <T> ArrayList<T> getAll(Class<T> clazz,String sql, Object... args) throws Exception {
// 1.创建连接
Connection connect = MyJdbcUtils.getConnect();
// 2.生成命令发射器
PreparedStatement pst = connect.prepareStatement(sql);
// 3.填充sql
for (int i = 1; i < args.length+1; i++) {
//设置?的值
//因为不知道sql中是否有?,以及?设置为什么值,通过形参来传入
//Object... args可变形参,可以传入0~n个值
//如果没有传入,说明没有?需要设置
//如果传入了n个值,那么说明sql中有n个?需要设置
// sql中的?是从1开始,而args的下标是从0开始
pst.setObject(i,args[i-1]);
}
// 4.执行sql
ResultSet resultSet = pst.executeQuery();
// 5.处理结果,将其封装为对应的对象
ResultSetMetaData metaData = resultSet.getMetaData(); //获取结果集中的元数据
int count = metaData.getColumnCount(); // 获取字段列数
ArrayList<T> lists = new ArrayList<>(); // 生成一个列表,用于存放对象
while (resultSet.next()){ // 获取到结果中的每一行记录
T t = clazz.newInstance(); // 获取JavaBean的实例对象
for (int i = 0; i < count; i++) {
String columnLabel = metaData.getColumnLabel(i + 1); // 获取字段名
Object value = resultSet.getObject(columnLabel); // 获取记录中对应字段的数据值
// 使用反射将数据值保存JavaBean实例对象的属性中
Field declaredField = clazz.getDeclaredField(columnLabel);
declaredField.setAccessible(true);
declaredField.set(t,value);
}
lists.add(t); // 将对象添加到集合中
}
MyJdbcUtils.closeConnect(connect,pst,resultSet);
return lists;
}
/**
* 通用的查询方法,适用于没有实体类的查询
* @param sql:待执行的sql
* @param args:传入的参数
* @return ArrayList: Map对象列表
*/
public ArrayList<Map> getAllToMap(String sql,Object... args) throws Exception{
// 1.获取连接
Connection connect = MyJdbcUtils.getConnect();
// 2.生成命令发射器
PreparedStatement pst = connect.prepareStatement(sql);
// 3.填充sql
for (int i = 1; i < args.length+1; i++) {
//设置?的值
//因为不知道sql中是否有?,以及?设置为什么值,通过形参来传入
//Object... args可变形参,可以传入0~n个值
//如果没有传入,说明没有?需要设置
//如果传入了n个值,那么说明sql中有n个?需要设置
// sql中的?是从1开始,而args的下标是从0开始
pst.setObject(i,args[i-1]);
}
// 4.执行sql
ResultSet resultSet = pst.executeQuery();
// 5.处理结果
ResultSetMetaData metaData = resultSet.getMetaData(); // 获取元数据
int count = metaData.getColumnCount(); // 获取字段列数
ArrayList<Map> lists = new ArrayList<>();
while (resultSet.next()){ // 获取每一条记录,进行处理
HashMap<String, Object> hashMap = new HashMap<>();
for (int i = 0; i < count; i++) {
String key = metaData.getColumnLabel(i + 1); // 获取字段名
Object value = resultSet.getObject(key); // 获取对应字段名的值
hashMap.put(key,value);
}
lists.add(hashMap);
}
MyJdbcUtils.closeConnect(connect,pst,resultSet);
return lists;
}
}
测试一下:
{avg(salary)=17258.22375, e_pid=1}
{avg(salary)=20250.29, e_pid=2}
{avg(salary)=15282.34, e_pid=3}
{avg(salary)=13644.253333, e_pid=4}
{avg(salary)=13932.08, e_pid=5}
{avg(salary)=8752.66, e_pid=6}
{avg(salary)=10256.3, e_pid=7}
{avg(salary)=7855.44, e_pid=8}
{avg(salary)=15466.88, e_pid=9}
{avg(salary)=3400.22, e_pid=10}
{avg(salary)=4000.33, e_pid=11}
Connection connect = null;
try {
connect = MyJdbcUtils.getConnect(); // 连接1
// 开启事务
connect.setAutoCommit(false);
// 执行sql
BasiDao1 basiDao1 = new BasiDao1();
basiDao1.basUpdate("update acount set money = money-500 where id = 1"); // 连接2
basiDao1.basUpdate("update acount set money = money+500 where id = 2"); // 连接3
connect.commit();
} catch (SQLException e) {
try {
connect.rollback(); // 失败就回滚
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
}
ThreadLocal用于保存某个线程共享变量,原因是在Java中,每一个线程对象中都有一个ThreadLocalMap,其key
就是一个ThreadLocal,而Object即为该线程的共享变量。而这个map是通过ThreadLocal的set和get方法操作的。对于同一个static
ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。
1、ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。
2、ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。
3、ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。
1.我们在调用getConnect时,不直接分配一个新的连接。而是使用ThreadLocal.get方法检查当前线程中的共享变量是否存在。
2.如果为null表明当前线程中还没有数据库连接。则创建一个新连接。并使用ThreadLocal.set方法把该连接对象设置为线程共
享变量
3.再次调用getConnect时,那么ThreadLocal.get方法返回的就不是null了。这时我们则不新建连接,而是将ThreadLocal
中的共享对象返回
4.当调用closeConnect释放连接时,将ThreadLocal中的共享对象也释放掉
我们在MyJdbcUtils 类中引入ThreadLocal
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
public class MyJdbcUtils {
private static String user;
private static String password;
private static String url;
private static String driver;
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
// 读取配置文件的内容,最好放到静态代码块中。这样只会在初始化类时,加载一次
// 而不会每生成一个实例化对象都调用一次,减少IO操作
static {
try {
Properties properties = new Properties();
//properties.load(new FileInputStream("E:\\Java全栈\\code\\JavaSE\\src\\jdbc.properties")); // 使用配置文件的绝对路径
properties.load(MyJdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties")); // 使用反射来使用配置文件的相对路径
user = properties.getProperty("user");
password = properties.getProperty("password");
url = properties.getProperty("url");
driver = properties.getProperty("driver");
System.out.println(url+"\t"+user+"\t"+password+"\t"+driver);
} catch (IOException e) {
e.printStackTrace();
}
}
// 连接数据库
public static Connection getConnect(){
try {
// 创建驱动
if (threadLocal.get() != null){
// 表示该线程已经有连接了
return threadLocal.get();
}else {
// 表示该线程还没有连接
Class.forName("com.mysql.cj.jdbc.Driver");
// 创建连接
String url = "jdbc:mysql://81.71.84.102:3306/test2";
Connection connection = DriverManager.getConnection(url, "root", "ouyi1994");
threadLocal.set(connection);
return connection;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return null;
}
// 关闭资源
public static void closeConnect(Connection connection, Statement statement, ResultSet resultSet){
if (connection != null){
try {
threadLocal.remove(); // 清除共享数据
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement!=null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
注意:现在的增删查改方法中就不能直接关闭connect了,只能在事务完成后关闭、
public int basUpdate(String sql,Object... args) throws SQLException {
// 1.获取连接
Connection connect = MyJdbcUtils.getConnect();
// 2.创建命令发射器
PreparedStatement preparedStatement = connect.prepareStatement(sql);
// 3.填充sql
for (int i = 1; i < args.length+1; i++) {
//设置?的值
//因为不知道sql中是否有?,以及?设置为什么值,通过形参来传入
//Object... args可变形参,可以传入0~n个值
//如果没有传入,说明没有?需要设置
//如果传入了n个值,那么说明sql中有n个?需要设置
// sql中的?是从1开始,而args的下标是从0开始
preparedStatement.setObject(i,args[i-1]);
}
// 4.执行sql
int i = preparedStatement.executeUpdate();
// 关闭资源
MyJdbcUtils.closeConnect(null,preparedStatement,null); // 这里就不能关闭connect了,因为事务可能还需要使用
return i;
}
测试代码:
class Demo13{
public static void main(String[] args){
Connection connect = null;
try {
connect = MyJdbcUtils.getConnect();
// 开启事务
connect.setAutoCommit(false);
// 执行sql
BasiDao1 basiDao1 = new BasiDao1();
basiDao1.basUpdate("update acount set money = money-500 where id = 1");
basiDao1.basUpdate("update acount set money = money-500 where id = 2");
connect.commit();
} catch (SQLException e) {
try {
connect.rollback(); // 失败就回滚
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
}finally {
MyJdbcUtils.closeConnect(connect,null,null); // 需要关闭连接
}
}
}
连接对象的缓冲区。负责申请,分配管理,释放连接的操作。
Connection对象在每次执行DML和DQL的过程中都要创建一次,DML和DQL执行完毕后,connection对象都会被销毁. connection对象是可以反复使用的,没有必要每次都创建新的.该对象的创建和销毁都是比较消耗系统资源的,如何实现connection对象的反复使用呢?使用连接池技术实现。
(1)加入jar包
例如:druid-1.1.10.jar
(2)代码步骤
第一步:建立一个数据库连接池
第二步:设置连接池的参数
第三步:获取连接
public class TestPool {
public static void main(String[] args) throws SQLException {
//1、创建数据源(数据库连接池)对象
DruidDataSource ds =new DruidDataSource();
//2、设置参数
//(1)设置基本参数
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/test");
ds.setUsername("root");
ds.setPassword("123456");
//(2)设置连接数等参数
ds.setInitialSize(5);//一开始提前申请好5个连接,不够了,重写申请
ds.setMaxActive(10);//最多不超过10个,如果10都用完了,还没还回来,就会出现等待
ds.setMaxWait(1000);//用户最多等1000毫秒,如果1000毫秒还没有人还回来,就异常了
//3、获取连接
for (int i = 1; i <=15; i++) {
Connection conn = ds.getConnection();
System.out.println("第"+i+"个:" + conn);
//如果这里没有关闭,就相当于没有还
conn.close();#这里关闭,是还回池中
}
}
}
配置 | 缺省 | 说明 |
---|---|---|
name | 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this) | |
url | 连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto | |
username | 连接数据库的用户名 | |
password | 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/使用ConfigFilter | |
driverClassName | 根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下) | |
initialSize | 0 | 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 |
maxActive | 8 | 最大连接池数量 |
maxIdle | 8 | 已经不再使用,配置了也没效果 |
minIdle | 最小连接池数量 | |
maxWait | 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 | |
poolPreparedStatements | false | 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。 |
maxOpenPreparedStatements | -1 | 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100 |
validationQuery | 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。 | |
testOnBorrow | true | 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 |
testOnReturn | false | 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 |
testWhileIdle | false | 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 |
timeBetweenEvictionRunsMillis | 有两个含义: 1)Destroy线程会检测连接的间隔时间2)testWhileIdle的判断依据,详细看testWhileIdle属性的说明 | |
numTestsPerEvictionRun | 不再使用,一个DruidDataSource只支持一个EvictionRun | |
minEvictableIdleTimeMillis | ||
connectionInitSqls | 物理连接初始化的时候执行的sql | |
exceptionSorter | 根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接 | |
filters | 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall | |
proxyFilters | 类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系 |