一、事务
事务的四大特性:
- 原子性(Atomicity)
+ 原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库操作。- 一致性(Consistency)
- 一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
- 拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
- 隔离性(Isolation)
- 隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
- 即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
- 持久化(Durability)
- 持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
- 一致性(Consistency)
小栗子说明事务
-
例子:A向B转账,转账这个流程中如果出现问题,事务可以让数据恢复成原来一样【A账户的钱没变,B账户的钱也没变】。
-
事例说明
try{
//开启事务,对数据的操作就不会立即生效。
connection.setAutoCommit(false);
//A账户减去500块
String sql = "UPDATE a SET money=money-500 ";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.executeUpdate();
//在转账过程中出现问题
int a = 3 / 0;
//B账户多500块
String sql2 = "UPDATE b SET money=money+500";
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.executeUpdate();
//如果程序能执行到这里,没有抛出异常,我们就提交数据
connection.commit();
//关闭事务【自动提交】
connection.setAutoCommit(true);
} catch (SQLException e) {
try {
//如果出现了异常,就会进到这里来,我们就把事务回滚【将数据变成原来那样】
connection.rollback();
//关闭事务【自动提交】
connection.setAutoCommit(true);
} catch (SQLException e1) {
e1.printStackTrace();
}
- 注意:当Connection遇到一个未处理的SQLException时,系统会非正常退出,事务也会自动回滚,但如果程序捕获到了异常,是需要在catch中显式回滚事务的。
savepoint
- 使用savepoint设置中间点。如果某地方出错了,我们设置中间点,回滚到出错之前即可。
注意 :savepoint不会结束当前事务,普通提交和回滚都会介素当前事务的。
事务的隔离级别
- 数据库定义了4个隔离级别:
- Serializable【可避免脏读,不可重复读,虚读】
- Repeatable read【可避免脏读,不可重复读】
- Read committed【可避免脏读】
- Read uncommitted【级别最低,什么都避免不了】
- 分别对应Connection类中的4个常量
- TRANSACTION_READ_UNCOMMITTED
- TRANSACTION_READ_COMMITTED
- TRANSACTION_REPEATABLE_READ
- TRANSACTION_SERIALIZABLE
- JDBC中设置隔离级别的部分代码
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try{
con = JdbcUntils.getConnection();
//设置该连接的隔离级别
con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
//开启事务
con.setAutoCommit(false);
}
- 隔离级别的设置只对当前链接有效。对于使用MySQL命令窗口而言,一个窗口就相当于一个链接,当前窗口设置的隔离级别只对当前窗口中的事务有效;对于JDBC操作数据库来说,一个Connection对象相当于一个链接,而对于Connection对象设置的隔离级别只对该Connection对象有效,与其他链接Connection对象无关。
二、改造JDBC工具类
- 问题:我们对数据库的增删改查都要连接数据库,关闭资源,获取PreparedSteatment对象,获取Connection对象此类的操作,这样的代码重复率是极高的,所以我们要对工具类进行增强
- 增删改
//我们发现,增删改只有SQL语句和传入的参数是不知道的而已,所以让调用该方法的人传递进来
//由于传递进来的参数是各种类型的,而且数目是不确定的,所以使用Object[]
public static void update(String sql, Object[] objects) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = getConnection();
preparedStatement = connection.prepareStatement(sql);
//根据传递进来的参数,设置SQL占位符的值
for (int i = 0; i < objects.length; i++) {
preparedStatement.setObject(i + 1, objects[i]);
}
//执行SQL语句
preparedStatement.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
- 查询
/*
1:对于查询语句来说,我们不知道对结果集进行什么操作【常用的就是把数据封装成一个Bean对象,封装成一个List集合】
2:我们可以定义一个接口,让调用者把接口的实现类传递进来
3:这样接口调用的方法就是调用者传递进来实现类的方法。【策略模式】
*/
//这个方法的返回值是任意类型的,所以定义为Object。
public static Object query(String sql, Object[] objects, ResultSetHandler rsh) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = getConnection();
preparedStatement = connection.prepareStatement(sql);
//根据传递进来的参数,设置SQL占位符的值
if (objects != null) {
for (int i = 0; i < objects.length; i++) {
preparedStatement.setObject(i + 1, objects[i]);
}
}
resultSet = preparedStatement.executeQuery();
//调用调用者传递进来实现类的方法,对结果集进行操作
return rsh.hanlder(resultSet);
}
- 接口
/*
* 定义对结果集操作的接口,调用者想要对结果集进行什么操作,只要实现这个接口即可
* */
public interface ResultSetHandler {
Object hanlder(ResultSet resultSet);
}
- 实现类
//接口实现类,对结果集封装成一个Bean对象
public class BeanHandler implements ResultSetHandler {
//要封装成一个Bean对象,首先要知道Bean是什么,这个也是调用者传递进来的。
private Class clazz;
public BeanHandler(Class clazz) {
this.clazz = clazz;
}
@Override
public Object hanlder(ResultSet resultSet) {
try {
//创建传进对象的实例化
Object bean = clazz.newInstance();
if (resultSet.next()) {
//拿到结果集元数据
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
for (int i = 0; i < resultSetMetaData.getColumnCount(); i++) {
//获取到每列的列名
String columnName = resultSetMetaData.getColumnName(i+1);
//获取到每列的数据
String columnData = resultSet.getString(i+1);
//设置Bean属性
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(bean,columnData);
}
//返回Bean对象
return bean;
}
三、分页
- 分页技术,特别是在搜索引擎下的搜索页面,不会把全部数据显示在一个页面中,所以出现了分页技术。
- Oracle实现分页
/*
Oracle分页语法:
@lineSize---每页显示数据行数
@currentPage----当前所在页
*/
SELECT *FROM (
SELECT 列名,列名,ROWNUM rn
FROM 表名
WHERE ROWNUM<=(currentPage*lineSize)) temp
WHERE temp.rn>(currentPage-1)*lineSize;
- Oracle分页原理
/*
Oracle分页:
Oracle的分页依赖于ROWNUM这个伪列,ROWNUM主要作用就是产生行号。
分页原理:
1:子查询查出前n行数据,ROWNUM产生前N行的行号
2:使用子查询产生ROWNUM的行号,通过外部的筛选出想要的数据
例子:
我现在规定每页显示5行数据【lineSize=5】,我要查询第2页的数据【currentPage=2】
注:【对照着语法来看】
实现:
1:子查询查出前10条数据【ROWNUM<=10】
2:外部筛选出后面5条数据【ROWNUM>5】
3:这样我们就取到了后面5条的数据
*/
- mysql实现分页
/*
Mysql分页语法:
@start---偏移量,不设置就是从0开始【也就是(currentPage-1)*lineSize】
@length---长度,取多少行数据
*/
SELECT *
FROM 表名
LIMIT [START], length;
/*
例子:
我现在规定每页显示5行数据,我要查询第2页的数据
分析:
1:第2页的数据其实就是从第6条数据开始,取5条
实现:
1:start为5【偏移量从0开始】
2:length为5
*/
四、使用JDBC连接数据库实现分页
- 需求
- 算出有多少页的数据,显示在页面上
- 根据页码,从数据库显示相应的数据
- 分析:
- 算出有多少页数据(需要世道每页显示多少,一共有多少数据)
- currentPage–当前页【由用户决定的】
- totalRecord–总数据数【查询表可知】
- lineSize–每页显示数据的数量【由我们开发人员决定】
- pageCount–页数【totalRecord和lineSize决定】
//每页显示3条数据
int lineSize = 3;
//总记录数
int totalRecord = getTotalRecord();
//假设用户指定的是第2页
int currentPage = 2;
//一共有多少页
int pageCount = getPageCount(totalRecord, lineSize);
//使用什么数据库进行分页,记得要在JdbcUtils中改配置
List<Person> list = getPageData2(currentPage, lineSize);
for (Person person : list) {
System.out.println(person);
}
//使用JDBC连接Mysql数据库实现分页
public static List<Person> getPageData(int currentPage, int lineSize) throws SQLException {
//从哪个位置开始取数据
int start = (currentPage - 1) * lineSize;
QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
String sql = "SELECT name,address FROM person LIMIT ?,?";
List<Person> persons = (List<Person>) queryRunner.query(sql, new BeanListHandler(Person.class), new Object[]{start, lineSize});
return persons;
}
//使用JDBC连接Oracle数据库实现分页
public static List<Person> getPageData2(int currentPage, int lineSize) throws SQLException {
//从哪个位置开始取数据
int start = (currentPage - 1) * lineSize;
//读取前N条数据
int end = currentPage * lineSize;
QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
String sql = "SELECT " +
" name, " +
" address " +
"FROM ( " +
" SELECT " +
" name, " +
" address , " +
" ROWNUM rn " +
" FROM person " +
" WHERE ROWNUM <= ? " +
")temp WHERE temp.rn>?";
List<Person> persons = (List<Person>) queryRunner.query(sql, new BeanListHandler(Person.class), new Object[]{end, start});
return persons;
}
public static int getPageCount(int totalRecord, int lineSize) {
//简单算法
//return (totalRecord - 1) / lineSize + 1;
//此算法比较好理解,把数据代代进去就知道了。
return totalRecord % lineSize == 0 ? (totalRecord / lineSize) : (totalRecord / lineSize) + 1;
}
public static int getTotalRecord() throws SQLException {
//使用DbUtils框架查询数据库表中有多少条数据
QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
String sql = "SELECT COUNT(*) FROM person";
Object o = queryRunner.query(sql, new ScalarHandler());
String ss = o.toString();
int s = Integer.parseInt(ss);
return s;
}