Day30 JDBC
- 事务机制
1.1 概述
数据库特有的术语,单个逻辑工作单元执行的一系列操作,同步发生数据更新时,防止数据的不一致
1.2 应用场景
设想网上购物的一次交易,其付款过程至少包括以下几步数据库操作:
· 更新客户所购商品的库存信息
· 保存客户付款信息–可能包括与银行系统的交互
· 生成订单并且保存到数据库中
· 更新用户相关信息,例如购物数量等等
正常的情况下,这些操作将顺利进行,最终交易成功,与交易相关的所有数据库信息也成功地更新。但是,如果在这一系列过程中任何一个环节出了差错,例如在更新商品库存信息时发生异常、该顾客银行帐户存款不足等,都将导致交易失败。一旦交易失败,数据库中所有信息都必须保持交易前的状态不变,比如最后一步更新用户信息时失败而导致交易失败,那么必须保证这笔失败的交易不影响数据库的状态–库存信息没有被更新、用户也没有付款,订单也没有生成。否则,数据库的信息将会一片混乱而不可预测。
数据库事务正是用来保证这种情况下交易的平稳性和可预测性的技术
1.3 无事务
以上是无事务案例,由于102 出错了,所以 没有添加进去,但是和 101、103 没有关系,依然可以添加成功,因为一条SQL就自动提交一次
1.4 有事务
到这里说明有错误 需要回滚(回到取消自动提交哪里),
回滚之后会把对应的缓存区清空 此时再提交就没问题
如果不回滚打开自动提交 那么201 203还是会被添加
因为没有提交之前的操作被保存在数据库缓冲区,
执行commit命令的时候才会强制刷新到数据库中
如果不回滚 等于没有使用事务 回滚会跳转到执行操作之前
- 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 实体类
2.2.3 Dao接口
2.2.4 Dao实现类之Statement
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
2.2.6 Dao实现类之PreparedStatement
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
- 硬代码优化
3.1 概述
像这些直接写死的数据,并且还有可能会更改的数据,称为硬代码
这种情况,如果我们想要更改,必须更改源码(.java文件),而我们运行其实是.class文件,所以一旦我们更改了源文件的情况,必须重新编译生成class文件才可以,并且需要重新进行部署
所以 我们需要处理掉硬代码
3.2 方案1-自定义解析规则
为什么需要重新部署? 因为更改java文件之后需要重新生成class文件
如果是txt文件呢? 没关系,更改一个项目中的txt文件,不需要重新部署
那么我们就可以把url、用户名,密码,驱动 都写到txt文件中
然后通过java程序中,使用IO流,对txt文件进行读取,然后再把值应用到程序当中
想要更改的时候,只需要更改该txt文件即可
1 指定规则
规定 : 键值对,键和值使用 = 隔开
2 解析规则
IO读取该文件(通过有参和无参构造,来定义文件名)
读取每一行数据
使用 = 分割 得到键值对
3 存储解析的值
使用Map存储
4 提供根据键获取 值的方法
入参String key ,出参 String value
/**
-
解析类
-
@author 天亮教育-帅气多汁你泽哥
-
@Date 2021年2月23日
*/
public class JDBCTxtUtil {// 单例
private static JDBCTxtUtil jdbcTxtUtil = null;// 用来存储解析的数据
private Map<String, String> data = new HashMap<String, String>();/**
- 解析
- @param fileName
*/
private JDBCTxtUtil(String fileName) {
// IO读取该文件(通过有参和无参构造,来定义文件名)
try (BufferedReader br = new BufferedReader(new FileReader("./src/"
+ fileName))) {
String tmp = null;
while ((tmp = br.readLine()) != null) {
String[] strs = tmp.split("=");
data.put(strs[0], strs[1]);
}
} catch (Exception e) {
e.printStackTrace();
}
// 读取每一行数据
// 使用 = 分割 得到键值对
}
/**
- 创建对象并解析,默认配置文件是 jdbc.txt
- 如果需要指定名字,请看下面的方法重载
- @return
*/
public static JDBCTxtUtil getInstance() {
return getInstance(“jdbc.txt”);
}
public static JDBCTxtUtil getInstance(String fileName) {
if (jdbcTxtUtil == null) {
synchronized (JDBCTxtUtil.class) {
if (jdbcTxtUtil == null) {
jdbcTxtUtil = new JDBCTxtUtil(fileName);
}
}
}
return jdbcTxtUtil;
}/**
- 对外提供根据key获取值的方法
- @param key
- @return
*/
public String get(String key) {
return data.get(key);
}
}
3.3 方案2-properties
- 连接池
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 使用方式
4.4 优化
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;
}
}