1.事务机制管理
1.1 Transaction事务机制管理
默认情况下是执行一条sql语句就保存一次,那么比如我们需要三条数据同时成功或同时失败就需要开启事务机制了。开启事务机制后执行过程中发生问题就会回滚到操作之前,相当于没有执行操作。
1.2 事务的特征
事务具有四个基本特性,通常称为ACID特性,分别是:
原子性(Atomicity):原子性指的是事务是不可分割的最小执行单元。事务中的所有操作要么全部提交成功,要么全部失败回滚,不存在部分提交或部分回滚的情况。原子性确保了数据库在执行事务时的完整性,即要么所有操作都成功,要么都不影响数据库状态。
一致性(Consistency):一致性指的是事务执行前后,数据库的状态必须保持一致。事务执行过程中可能改变数据库中的数据,但是这些改变必须满足数据库定义的所有约束和规则,确保数据库从一个一致性状态转换到另一个一致性状态。
隔离性(Isolation):隔离性指的是并发执行的多个事务之间是相互隔离的,一个事务的执行不应该受到其他事务的影响。隔离性保证了每个事务在执行时都拥有独立的数据空间,不会互相干扰,避免了并发事务之间的数据竞争和不一致性问题。
持久性(Durability):持久性指的是一旦事务提交成功,其所做的修改将永久保存在数据库中,并且不会因系统故障而丢失。即使系统崩溃或断电,已提交的事务对数据库的修改也应该被永久保存。持久性确保了数据的可靠性和持久性,是数据库系统的重要特性之一。
这些特性确保了事务在数据库中的可靠性、一致性和完整性,是数据库管理系统实现数据管理和控制的基础。
1.3 事务处理操作
public class JDBC_Transction {
public static void main(String[] args) {
// noTransaction();
useTransaction();
}
// 未使用事务
// 当第二个sql语句有错误时,一三语句同样会执行
public static void noTransaction() {
Connection conn = null;
Statement statement = null;
try {
conn = DBUtil.getConnection();
statement = conn.createStatement();
statement.addBatch("insert into user(name,password,nickname) values('test1',111,'用户1')");
statement.addBatch("insert into user(name,pass,nickname) values('test2',222,'用户2')");
statement.addBatch("insert into user(name,password,nickname) values('test3',333,'用户3')");
statement.executeBatch();
System.out.println("添加成功");
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(statement);
DBUtil.close(conn);
}
}
// 使用事务
// 第二句sql语句有错,第一三条数据也不会执行
public static void useTransaction() {
Connection conn = null;
Statement statement = null;
try {
conn = DBUtil.getConnection();
statement = conn.createStatement();
// 关闭自动提交,开启事务
conn.setAutoCommit(false);
statement.addBatch("insert into user(name,password,nickname) values('test1',111,'用户1')");
statement.addBatch("insert into user(name,pass,nickname) values('test2',222,'用户2')");
statement.addBatch("insert into user(name,password,nickname) values('test3',333,'用户3')");
statement.executeBatch();
// 执行完成后,提交并开启自动提交
conn.commit();
conn.setAutoCommit(true);
System.out.println("添加成功");
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
// 执行到这里说明有错
if (conn != null) {
try {
// 事务回滚,回到操作之前的状态
// 同时删除操作对应的缓冲区中的数据
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
} finally {
DBUtil.close(statement);
DBUtil.close(conn);
}
}
}
2.Properties优化硬编码
public static Connection getConnection() throws ClassNotFoundException, SQLException{
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/_day_02","root", "abc280619SS");
return conn;
}
如上代码,我们项目开发完成测试通过后,通常是要打包发布给别人使用的,如果我们把一些配置信息写死到代码中(这种行为叫硬编码,hardcode),别人就无法修改了
比如别人使用的MySQL服务器对应的IP是10.0.0.1,端口号是9300,用户名是mysqluser、密码是123456。
那么我们只能改代码再重新打包发布,如果有多个用户,他们对应的配置信息都不同,那么我们要针对不同的用户打包发布多次。
以上显然是没必要的,因为我们开发的是程序,只要业务需求/逻辑不变,我们就无需多次打包发布。对此我们的解决方案是,尽量避免硬编码, 将数据与程序分离解耦,将配置信息存储到配置文件中,程序运行时读取配置文件,不同用户只需按自己的实际情况修改配置文件即可。
2.1 创建jdbc.properties文件
这种方式下,账号密码发生变动时,只需要更改配置文件中的内容即可。
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/_day_02
username=root
password=root
2.2 创建PropertiesUtil
public class PropertieUtil {
private static Properties properties = null;
static {
try {
// propertie 本质就是一个map,可以通过key获取value
properties = new Properties();
properties.load(Properties.class.getClassLoader().getResourceAsStream("jdbc.properties"));
} catch (Exception e) {
e.printStackTrace();
}
}
// 提供获取value的方法
public static String get(String key) {
return properties.getProperty(key);
}
}
2.3 更改DBUtil工具类
public class DBUtil_00 {
// 封装方法加载驱动创建链接
public static Connection getConnection() throws ClassNotFoundException, SQLException{
// 通过PropertiesUtil获取配置文件中的值
Class.forName(PropertieUtil.get("driver"));
String url = PropertieUtil.get("url");
String username = PropertieUtil.get("username");
String password = PropertieUtil.get("password");
Connection conn = DriverManager.getConnection(url,username,password);
return conn;
}
// 封装方法关闭资源
public static void close(AutoCloseable obj) {
if (obj != null) {
try {
obj.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
3. 连接池
3.1 介绍
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;
释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。
这项技术能明显提高对数据库操作的性能。
3.2 应用场景
场景一:电商网站如淘宝,京东的双十一活动,同一时刻会有上亿甚至上十亿的用户访问数据库。
场景二:某服务器上除了运行MYSQL服务,还有其他一些服务比如WEB服务。我们知道,数据库连接的创建维持不只消耗我们客户端(个人PC)的系统资源(CPU、内存、IO设备),更消耗服务器的系统资源,而假如我们在周末时并不会去访问数据库,这时候服务器上依然还维持着一条空闲的连接,假设占用了2M内存,现在服务器上内存已经都被分配出去了,WEB服务却要求新申请1M内存,很显然,由于内存不足,WEB服务就无法正常运行了。
以上两种情况下,如果只使用上面的代码是无法支持的。连接池的引入,则主要解决了以上两类问题:
1. 能给多用户分配链接或给一个用户分配多个链接
2. 在适当的时候回收空闲链接以节省系统资源
JDBC连接池有一个对应的接口javax.sql.DataSource。它也是一个标准一个规范,目前实现了这套规范的连接池产品主要有:DBCP(MyBatis通常使用这个连接池)、C3P0(Hibernate通常使用这个连接池)、JNDI(Tomcat的默认连接池)。
以DBCP为例:
DBCP内部提供了一个“池子”,程序启动的时候,先创建一些连接对象放到这个池子中,备用,当调用连接池的getConnection()方法时,就从池子取出连接对象分配给多个用户/线程。使用完毕调用close()方法时,DBCP重写了close方法,它并不真正关闭连接,而是返还到池子中,由DBCP自动管理池子中的连接对象,在适当的时候真正关闭这些连接。
优点 :
资源复用 : 数据库连接得到重用,避免了频繁创建释放链接引起的大量性能开销,在减少系统消耗的基础上,也增进了系统运行环境的平稳性
更快的系统响应速度 : 数据库连接池在初始化过程中,往往就已经创建了若干个数据库连接对象放到池中备用。这时,连接的初始化工作已完成,对于业务请求处理而言,直接利用现有的可用连接,避免了数据库连接初始化和释放过程的时间,从而缩减了系统整体的响应时间
统一的连接管理,避免数据库连接遗漏 : 在较为完备数据库连接池中,可以根据预先的连接占用超时设定,强制回收占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露情况
因为连接池组件不是JDK自带的,所以要导入相关jar包,DBCP相关的吉安人、
包有三个,如下:
3.3 创建连接池工具类
public class BasicDataSorceUtil {
private static BasicDataSource basicDataSource;
// 为了解决高并发的问题,使用线程安全的单例模式,以双重锁校验的懒汉模式为例
// 提供获取线程池对象的方法
public static BasicDataSource getBasicDataSource() {
if (basicDataSource == null) {
synchronized (BasicDataSource.class) {
if (basicDataSource == null) {
basicDataSource = new BasicDataSource();
basicDataSource.setDriverClassName(PropertieUtil.get("driver"));
basicDataSource.setUrl(PropertieUtil.get("url"));
basicDataSource.setUsername(PropertieUtil.get("username"));
basicDataSource.setPassword(PropertieUtil.get("password"));
}
}
}
return basicDataSource;
}
}
3.4 测试线程池链接
// 使用数据库连接池
public class DBUtil_01 {
public static void main(String[] args) throws SQLException {
// 获取连接池对象
BasicDataSource bds = BasicDataSorceUtil.getBasicDataSource();
// 通过连接池获取链接对象
Connection conn = bds.getConnection();
PreparedStatement ps = conn.
prepareStatement("insert into user(name,password,nickname) values('test4',444,'用户4')");
ps.executeUpdate();
ps.close();
conn.close();
}
}