Day30 JDBC
1. 事务机制
1.1 概述
数据库特有的术语,单个逻辑工作单元执行的一系列操作,同步发生数据更新时,防止数据的不一致
1.2 应用场景
设想网上购物的一次交易,其付款过程至少包括以下几步数据库操作:
· 更新客户所购商品的库存信息
· 保存客户付款信息–可能包括与银行系统的交互
· 生成订单并且保存到数据库中
· 更新用户相关信息,例如购物数量等等
正常的情况下,这些操作将顺利进行,最终交易成功,与交易相关的所有数据库信息也成功地更新。但是,如果在这一系列过程中任何一个环节出了差错,例如在更新商品库存信息时发生异常、该顾客银行帐户存款不足等,都将导致交易失败。一旦交易失败,数据库中所有信息都必须保持交易前的状态不变,比如最后一步更新用户信息时失败而导致交易失败,那么必须保证这笔失败的交易不影响数据库的状态–库存信息没有被更新、用户也没有付款,订单也没有生成。否则,数据库的信息将会一片混乱而不可预测。
数据库事务正是用来保证这种情况下交易的平稳性和可预测性的技术
1.3 无事务
package com.JDBCTest_02.com;
import java.sql.Connection;
import java.sql.Statement;
import com.JDBCTest_02.util.DBUtil;
/**
* 说明执行多个插入语句,当有个语句错误,依然会插入正确语句并执行。自己提交事务
* setAutoCommit() : 设置是否自动提交
*/
public class JDBC_01_NoTransaction {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
try {
// 链接对象
connection = DBUtil.getConnection();
// 语句对象
statement = connection.createStatement();
statement
.addBatch("insert into test(id,name) values(101,'无事务测试1')");
// 这里name写错了 nam
statement.addBatch("insert into test(id,nam) values(102,'无事务测试2')");
statement
.addBatch("insert into test(id,name) values(103,'无事务测试3')");
// 执行
statement.executeBatch();
System.out.println("执行成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtil.close(statement);
DBUtil.close(connection);
}
}
}
以上是无事务案例,由于102 出错了,所以 没有添加进去,但是和 101、103 没有关系,依然可以添加成功,因为一条SQL就自动提交一次
1.4 有事务
package com.JDBCTest_02.com;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import com.JDBCTest_02.util.DBUtil;
/**
* setAutoCommit() : 设置是否自动提交
*
*/
public class JDBC_02_Transaction {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
try {
// 链接对象
connection = DBUtil.getConnection();
// 取消自动提交
connection.setAutoCommit(false);
// 语句对象
statement = connection.createStatement();
statement
.addBatch("insert into test(id,name) values(201,'有事务测试1')");
// 这里name写错了 nam
statement.addBatch("insert into test(id,nam) values(202,'有事务测试2')");
statement
.addBatch("insert into test(id,name) values(203,'有事务测试3')");
// 执行
statement.executeBatch();
System.out.println("执行成功");
// 提交
connection.commit();
// 打开自动提交
connection.setAutoCommit(true);
} catch (Exception e) {
e.printStackTrace();
try {
// 到这里说明有错误,需要回滚
// 如果不回滚,直接打开自动提交的话,那么201和203还是会被添加进去
// 因为没有提交之前的操作被保存到数据库缓存区,执行commit命令的时候,才会强制刷新到数据库中
// 如果不会滚,等于没有使用事务一样,回滚就会跳转到执行操作之前
// 所以 回滚之后会把对应的缓存区清空,然后再开启自动提交,就不会出现问题了
connection.rollback();//一当出现错误语句,就回到刚开始位置,使得数据正确性
// 打开自动提交
connection.setAutoCommit(true);
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
DBUtil.close(statement);
DBUtil.close(connection);
}
}
}
2. SQL注入
2.1 概述
SQL注入即是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。
2.2 案例
2.2.1 示例数据
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of t_user
-- ----------------------------
INSERT INTO `t_user` VALUES ('1', 'admin', 'root');
INSERT INTO `t_user` VALUES ('2', 'test1', '123');
INSERT INTO `t_user` VALUES ('3', 'test2', 'root');
2.2.2 实体类
package com.JDBCTest_02.com._03_payload.pojo;
public class User {
private int id;
private String username;
private String password;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public User(String username, String password) {
super();
this.username = username;
this.password = password;
}
}
2.2.3 Dao接口
package com.JDBCTest_02.com._03_payload.dao;
import com.JDBCTest_02.com._03_payload.pojo.User;
public interface IUserDao {
/**
* 登陆
*
* @param user
*/
public void login(User user);
/**
* 更改密码
*
* @param user
* @param newPwd
*/
public void updatePassword(User user, String newPwd);
}
2.2.4 Dao实现类之Statement
package com.JDBCTest_02.com._03_payload.dao.impl;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import com.JDBCTest_02.com._03_payload.dao.IUserDao;
import com.JDBCTest_02.com._03_payload.pojo.User;
import com.JDBCTest_02.util.DBUtil;
/*
* sql注入
* */
public class UserDao_Statement implements IUserDao {
@Override
public void login(User user) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
connection = DBUtil.getConnection();
statement = connection.createStatement();
String sql = "select * from t_user where username = '"
+ user.getUsername() + "' and password = '"
+ user.getPassword() + "'";
System.out.println(sql);
resultSet = statement.executeQuery(sql);
if (resultSet.next()) {
System.out.println("登陆成功");
} else {
System.out.println("用户名或密码不正确");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtil.close(resultSet);
DBUtil.close(statement);
DBUtil.close(connection);
}
}
@Override
public void updatePassword(User user, String newPwd) {
Connection connection = null;
Statement statement = null;
try {
connection = DBUtil.getConnection();
statement = connection.createStatement();
String sql = "update t_user set password = '" + newPwd
+ "' where username = '" + user.getUsername() + "'";
System.out.println(sql);
int count = statement.executeUpdate(sql);
System.out.println("修改成功,影响了 " + count + " 条数据");
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtil.close(statement);
DBUtil.close(connection);
}
}
}
2.2.5 测试类之Statement
package com.JDBCTest_02.com._03_payload.test;
import com.JDBCTest_02.com._03_payload.dao.impl.UserDao_Statement;
import com.JDBCTest_02.com._03_payload.pojo.User;
public class UserDao_StatementTest {
public static void main(String[] args) {
// dao引用
UserDao_Statement userDao = new UserDao_Statement();
// 可以成功
User user1 = new User("admin", "root");
// 密码错误
User user2 = new User("admin", "123");
// SQL注入 可以登陆成功
// select * from t_user where username = 'admin' and password = '' or 1='1'
User user3 = new User("admin", "' or 1='1");
// 登陆
userDao.login(user1);
userDao.login(user2);
userDao.login(user3);
// 更改密码
userDao.updatePassword(new User("admin", "root"), "1234");
// SQL注入,三条用户的密码都更改
userDao.updatePassword(new User("' or 1='1", "root"), "xxxxx");
}
}
2.2.6 Dao实现类之PreparedStatement
package com.JDBCTest_02.com._03_payload.dao.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import com.JDBCTest_02.com._03_payload.dao.IUserDao;
import com.JDBCTest_02.com._03_payload.pojo.User;
import com.JDBCTest_02.util.DBUtil;
public class UserDao_PreparedStatement implements IUserDao {
@Override
public void login(User user) {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
connection = DBUtil.getConnection();
String sql = "select * from t_user where username =? and password = ?";
statement = connection.prepareStatement(sql);
statement.setString(1, user.getUsername());
statement.setString(2, user.getPassword());
resultSet = statement.executeQuery();
if (resultSet.next()) {
System.out.println("登陆成功");
} else {
System.out.println("用户名或密码不正确");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtil.close(resultSet);
DBUtil.close(statement);
DBUtil.close(connection);
}
}
@Override
public void updatePassword(User user, String newPwd) {
Connection connection = null;
PreparedStatement statement = null;
try {
connection = DBUtil.getConnection();
String sql = "update t_user set password =? where username = ?";
statement = connection.prepareStatement(sql);
statement.setString(1, newPwd);
statement.setString(2, user.getUsername());
int count = statement.executeUpdate();
System.out.println("修改成功,影响了 " + count + " 条数据");
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtil.close(statement);
DBUtil.close(connection);
}
}
}
2.2.7 测试类之PreparedStatement
package com.JDBCTest_02.com._03_payload.test;
import com.JDBCTest_02.com._03_payload.dao.IUserDao;
import com.JDBCTest_02.com._03_payload.dao.impl.UserDao_PreparedStatement;
import com.JDBCTest_02.com._03_payload.pojo.User;
public class UserDao_PreparedStatementTest {
public static void main(String[] args) {
// dao引用
IUserDao userDao = new UserDao_PreparedStatement();
// 可以成功
User user1 = new User("admin", "root");
// 密码错误
User user2 = new User("admin", "123");
// SQL注入 登陆失败
User user3 = new User("admin", "' or 1='1");
// 登陆
userDao.login(user1);
userDao.login(user2);
userDao.login(user3);
// 更改密码
userDao.updatePassword(new User("admin", "root"), "1234");
// SQL注入,更改不了,本质 就是把 ' 单引号进行转义
userDao.updatePassword(new User("' or 1='1", "root"), "xxxxx");
}
}
3.3 方案2-properties
package com.JDBCTest_03_Properties.util;
import java.io.IOException;
import java.util.Properties;
import com.JDBCTest_03_Properties.test.Test;
/*
*单列模式 懒汉 线程安全*/
public class PropertiesUtil {
private PropertiesUtil() {
}
private static Properties properties = null;
public static Properties getProperties() {
return getProperties("jdbc.properties");
}
public static Properties getProperties(String fileName) {
// System.out.println(fileName);//jdbc.properties
if (properties == null) {
properties = new Properties();
try {
properties.load(Thread.currentThread().getContextClassLoader().getSystemResourceAsStream(".//com//JDBCTest_03_Properties//"+fileName));
// properties.load(Test.class.getClassLoader().getResourceAsStream(".//com//JDBCTest_03_Properties//" + fileName));
} catch (IOException e) {
e.printStackTrace();
}
}
return properties;
}
}
测试还是用上面的测试
4. 连接池
4.1介绍
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;
释放,空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。
这项技术能明显提高对数据库操作的性能
4.2 应用场景
上面我们抽取了样板代码,将配置信息与代码分离解耦。JDBC编程的效率从一定程度上提高了,如果我们只是为了学习或者开发一些小的简单的项目,到这个地步其实就足够了。如果我们要想更上一层楼,支持如下场景:
场景一:电商网站如淘宝、京东每年都会有双11这种活动,同一时刻,会有上亿甚至上十亿的用户访问数据库(因为要生成订单等数据),如果我们还使用上面的思路,显然是捉襟见肘。
场景二:某服务器上除了运行MYSQL服务,还有其他一些服务比如WEB服务。我们知道,数据库连接的创建维持不只消耗我们客户端(个人PC)的系统资源(CPU、内存、IO设备),更消耗服务器的系统资源,而假如我们在周末时并不会去访问数据库,这时候服务器上依然还维持着一条空闲的连接,假设占用了2M内存,现在服务器上内存已经都被分配出去了,WEB服务却要求新申请1M内存,很显然,由于内存不足,WEB服务就无法正常运行了。
以上2个场景,我们上面的代码,是无法支持的。
连接池的引入,则主要解决了以上2类问题:
1.能给多用户分配连接或者给一个用户分配多个连接;
2.在适当的时候回收空闲连接以节省系统资源。
JDBC连接池有一个对应的接口
javax.sql.DataSource。它也是一个标准一个规范,目前实现了这套规范的连接池产品主要有:
DBCP(MyBatis通常使用这个连接池)、C3P0(Hibernate通常使用这个连接池)、JNDI(Tomcat的默认连接池)。
我们使用DBCP来讲解。
DBCP内部提供了一个“池子”,程序启动的时候,先创建一些连接对象放到这个池子中,备用,当调用连接池的getConnection()方法时,就从池子取出连接对象分配给多个用户/线程。使用完毕调用close()方法时,DBCP重写了close方法,它并不真正关闭连接,而是返还到池子中,由DBCP自动管理池子中的连接对象,在适当的时候真正关闭这些连接。
优点 :
1.资源复用 : 数据库连接得到重用,避免了频繁创建释放链接引起的大量性能开销
在减少系统消耗的基础上,也增进了系统运行环境的平稳性
2.更快的系统响应速度 : 数据库连接池在初始化过程中,往往就已经创建了若干个数据库连接对象放到池中备用
这时,连接的初始化工作已完成,对于业务请求处理而言,直接利用现有的可用连接,避免了数据库连接初始化和释放过程的时间,从而缩减了系统整体的响应时间
3.统一的连接管理,避免数据库连接遗漏 : 在较为完备数据库连接池中,可以根据预先的连接占用超时设定,强制回收占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露情况
因为连接池组件不是JDK自带的,所以要导入相关的jar包。DBCP相关的jar包有3个,如下:
4.3 使用方式
package com.JDBCTest_03_Properties.test;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import org.apache.commons.dbcp2.BasicDataSource;
import com.JDBCTest_03_Properties.util.PropertiesUtil;
public class TestDBCP {
public static void main(String[] args) throws SQLException {
// 创建连接池对象
BasicDataSource bds = new BasicDataSource();
// 配置数据库信息
Properties properties = PropertiesUtil.getProperties();
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
String username = properties.getProperty("username");
String password = properties.getProperty("password");
bds.setDriverClassName(driver);
bds.setUrl(url);
bds.setUsername(username);
bds.setPassword(password);
bds.setMaxTotal(4);
// 获取链接
Connection conn = bds.getConnection();
System.out.println(conn);
}
}
4.4 优化
package com.JDBCTest_03_Properties.util;
import java.util.Properties;
import org.apache.commons.dbcp2.BasicDataSource;
public class BasicDataSourceUtil {
private BasicDataSource bds = null;
private BasicDataSourceUtil() {
// 创建连接池对象
bds = new BasicDataSource();
// 配置数据库信息
Properties properties = PropertiesUtil.getProperties();
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
String username = properties.getProperty("username");
String password = properties.getProperty("password");
bds.setDriverClassName(driver);
bds.setUrl(url);
bds.setUsername(username);
bds.setPassword(password);
}
private static BasicDataSourceUtil bdsu = null;
// 主要解决高并发问题,所以单例一定是线程安全
public static BasicDataSourceUtil getInstance() {
if (bdsu == null) {
synchronized (BasicDataSourceUtil.class) {
if (bdsu == null) {
bdsu = new BasicDataSourceUtil();
}
}
}
return bdsu;
}
// 获取连接池
public BasicDataSource getBasicDataSource() {
return bds;
}
}
测试
package com.JDBCTest_03_Properties.com;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.apache.commons.dbcp2.BasicDataSource;
import com.JDBCTest_03_Properties.util.BasicDataSourceUtil;
import com.JDBCTest_03_Properties.util.DBUtil;
public class JDBC_03_BDSUTest {
public static void main(String[] args) {
BasicDataSource bds = BasicDataSourceUtil.getInstance()
.getBasicDataSource();
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
connection = bds.getConnection();
String sql = "insert into test (id,name) values (?,?)";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1, 1);
preparedStatement.setString(2, "张三");
preparedStatement.executeUpdate();
System.out.println("执行完成");
} catch (SQLException e) {
e.printStackTrace();
}finally{
DBUtil.close(connection);
}
}
}