JDBC的应用(二)

一、JDBC 事务操作

事务在 JDBC 中非常重要,没有事务是一件非常恐怖的事情,如下案例

1.1 银行转账案例

需求: 银行转账, 从凯哥账户上给赵云转 1000 块钱.

1.1.1 准备 account(账户)

id

name(账号,唯一)

balance(余额)

1

凯哥

20000

2

赵云

0

CREATE TABLE account(id BIGINT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(20),balance DECIMAL(10,2)  );

 INSERT INTO account(name,balance) VALUES('张无忌',20000),VALUES('赵敏',0);

1.1.2 转账操作步骤

  1. 查询凯哥的账户余额是否大于等于1000.

SQL : SELECT * FROM account WHERE balance >= 1000 AND name='凯哥

 余额小于1000 : 温馨提示: 亲,你的余额不足.

余额大于等于1000 : GOTO 2.

  1. 从凯哥的账户余额中减少1000

SQL : UPDATE account SET balance = balance - 1000 WHERE name='凯哥'

  1. 在赵云的账户余额中增加1000

SQL : UPDATE account SET balance = balance +1000 WHERE name='赵云'

案例实现

@Test

public void testTx() throws Exception {

// 1 查询凯哥的账户余额是否大于等于1000

Connection conn = JDBCUtil.getConnection();

String sql = "SELECT * FROM  account WHERE balance>=? AND name=?";

PreparedStatement pst = conn.prepareStatement(sql);

//?设置数据

pst.setBigDecimal(1,new BigDecimal("1000"));

pst.setString(2,"凯哥");

ResultSet rs = pst.executeQuery();

if(!rs.next()){

System.out.println("余额不足");

return;

}

// 2 从凯哥的账户余额中减少1000.

sql = "UPDATE account SET balance = balance-? WHERE name=?";

pst = conn.prepareStatement(sql);

//设置? 的数据

pst.setBigDecimal(1,new BigDecimal("1000"));

pst.setString(2,"凯哥");

pst.executeUpdate();

// 模拟出异常或停电

int a = 10/0;

// 3 赵云的账户余额中增加1000.

sql = "UPDATE account SET balance = balance+? WHERE name=?";

pst = conn.prepareStatement(sql);

//设置? 的数据

pst.setBigDecimal(1,new BigDecimal("1000"));

pst.setString(2,"赵云");

pst.executeUpdate();

// 释放资源

JDBCUtil.close(conn,pst,rs);

}

问题: 当程序执行到第 ②步和第 ③步中间时,突然出现一个异常或停电,此时会造成凯哥减了1000,而赵云未加1000的问题.

造成这个问题的根本原因是,加减是两个单独的操作,加失败,减已经被执行,而转账业务中,需要保证加减两个操作要么都成功,要么都失败.

所以这里我们需要使用 InnoDB 存储引擎,用事务管理来解决这个问题。

1.2 JDBC 的事务操作

事务(Transaction): 简写为tx

事务是指将一组操作括为一个单元,为确保数据库中数据的一致性,数据操作是成组的单元,当单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态。

1.2.1 事务的 ACID 属性

  1. 原子性(Atomicity: 原子在化学中,是最小单位,不可以再分割了. 原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
  2. 一致性(Consistency): 保证数据的完整性. 事务必须使数据库从一个一致性状态变换到另外一个一致性状态。(数据不被破坏)
  3. 隔离性(Isolation: 事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
  4. 持久性(Durability: 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响

1.2.2 事务的操作步骤

  1. 先定义开始一个事务,然后对数据作修改操作,
  2. 执行过程中,如果没有问题就提交(commit)事务,此时的修改将永久地保存下来
  3. 如果执行过程中有问题(异常),回滚事务(rollback),数据库管理系统将放弃您所作的所有修改而回到开始事务时的状态。

1.2.3 事务的操作模板

try{

//取消事务自动提交机制,设置为手动提交

connection对象.setAutoCommit(false);

//操作1

//操作2

//异常

//操作3

//....

//所有操作成功则 手动提交事务

connection对象.commit();

}catch(Exception e){

//处理异常

//出现异常 回滚事务

connection对象.rollback();

}

1.2.4 转账案例优

@Test

public void testTx() throws Exception {

Connection conn = null;

PreparedStatement pst = null;

ResultSet rs = null;

try{

//1 查询凯哥的账户余额是否大于等于1000 conn = JDBCUtil.getConnection();

//事务设置为手动提交

conn.setAutoCommit(false);

String sql = "SELECT * FROM account WHERE balance>=? AND name=?";

pst = conn.prepareStatement(sql);

//给 ? 设置数据

pst.setBigDecimal(1,new BigDecimal("1000")); pst.setString(2,"凯哥"); rs = pst.executeQuery();

if(!rs.next()){

System.out.println("余额不足");

return;

}

//2 从凯哥的账户余额中减少1000.

sql = "UPDATE account SET balance = balance-? WHERE name=?"; pst = conn.prepareStatement(sql);

//设置 ? 的数据

pst.setBigDecimal(1,new BigDecimal("1000")); pst.setString(2,"凯哥"); pst.executeUpdate();

//模拟出异常或停电 int a = 10/0;

//3 在赵云的账户余额中增加1000.

sql = "UPDATE account SET balance = balance+? WHERE name=?"; pst = conn.prepareStatement(sql);

//设置 ? 的数据

pst.setBigDecimal(1,new BigDecimal("1000")); pst.setString(2,"赵云"); pst.executeUpdate();

//没问题,提交事务 conn.commit();

}catch(Exception e){

//出现异常 回滚事务

connection对象.rollback();

}finally{

释放资源

JDBCUtil.close(conn,pst,rs);

}

}

1.2.5 事务相关注意事项

默认情况下,事务在执行完 DML 操作就自动提交.

查询操作,其实是不需要事务的.但是,一般的,我们在开发中都把查询放入事务中.

开发中,代码完全正确,没有异常,但是就是数据库中数据不变

意识 : 没有提交事务

 MySQL ,只有 InnoDB 存储引擎支持事务,支持外键,MyISAM 不支持事务.

以后事务我们不应该在 DAO 层处理,应该在 service 层控制(了解).

事务在讲解 MyBatis,Spring,项目的时候都会再讲.

二、连接池思想

2.1 连接池引入和介绍

普通的 JDBC 数据库连接(Connectiond对象)使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证用户名和密码得花费 0.05s1s 的时间, 时间成本比较大 

需要数据库连接时,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。

数据库连接对象并没有得到很好的重复利用.若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。

对于每一次数据库连接,使用完后都得断开,不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,可能导致内存泄漏,服务器崩溃.

解决: 先创建好 Connection 对象,存起来重复使用,如下图.存了 Connection 的容器则为连接池.

连接池属性分析:联想春运去火车站购票

基本属性:连接池存了连接对象,而连接对象依赖四要素,所以四要素是基本要求

driverClassName,url,username,password

其他属性:对连接对象做限制的配置

 最多连接数:10 在连接池中最多有10Connection对象,其他客户端进入等待状

 最少连接数 : 3 在连接池中最少存在3Connection对象

 最长等待时间:5 min 使用5分钟来申请获取 Connection 对象,如果时间到还没有申请到,则提示,自动放弃

 最长超时时间:10min 如果你在10分钟之内没有任何动作,则认为是自动放弃 Connection 对象.

 Java ,连接池使用 javax.sql.DataSource 接口来表示连接池. DataSource(数据源)和连接池 (Connection Pool)是同一个.

注意 :DataSource 仅仅只是一个接口,由各大服务器厂商来实现(Tomcat,JBoss).

2.2 常见的 DataSource 实现

DBCP : Spring 框架推荐的 

C3P0 : Hibernate 框架推荐的 

druid : 阿里巴巴的连接池(号称 Java 语言中性能最好的连接池).

2.3 使没使用连接池的区别

如何获取 Connection 对象:

没有使用连接池: Connection conn =DriverManager.getConnection(url,username,password);

使用 连接池:Connection conn = DataSource对象.getConnection();

只要获取了Connection对象,接下来的操作和以前是一模一样的.

2.如何释放 Connection对象(Connection对象.close()):

 没有使用连接池: 是和数据库服务器断开.

 使用连接池: 是把Connection对象返还给连接池中,并没有和数据库服务器断开.

关键在于:如何创建 DataSource 对象,所以需要来学习 DataSource 实现的使用.

2.4 druid 连接池使用

druid:是阿里巴巴研发出来的号称 Java语言领域性能最高的连接池. wiki址:https://github.com/alibaba/druid/wiki

使用起来,类似于 DBCP 连接池. 方便检测性能/状态. 支持: MySQL,Oracle,DB2,MS Server. 支持: 对配置文件的密码加密. 拷贝 jar: druid-1.0.15.jar.

最基本的写法:

@Test

public void testDruidDataSource() throws Exception {

// 创建一个连接池对象

DruidDataSource ds = new DruidDataSource();

ds.setDriverClassName("com.mysql.jdbc.Driver");

ds.setUrl("jdbc:mysql://localhost:3306/jdbcdemo");

ds.setUsername("root");

ds.setPassword("admin");

ds.setInitialSize(5);//初始化创建连接的个数

Connection conn = ds.getConnection();

System.out.println(conn.getClass());

}

处理 Druid 连接池使用过程中的硬编码

在创建连接池对象时,使用到的连接数据库的信息应该编写到 Properties 配置文件中,然后再读取到内存中来使用

db.properties

#这里的 key 一定要和 DruidDataSource 中对应的属性名一致

 driverClassName=com.mysql.jdbc.Driver

 url=jdbc:mysql://localhost:3306/javaweb 4 username=root

   password=admin

三、SQL 注入详解

Statement  PreparedStatement 的区别

Statement  PreparedStatement 的区别: PreparedStatement 存在的优势:

  1. 更好的可读性,可维护性.
  2. 可以提供更好的性能(预编译). MySQL 不支持 PreparedStatement 性能优化.
  3. 更安全,可以防止 SQL 注入的问题

如果使用 Statement 语句对象,我们是把参数直接拼接到 SQL 中然后执行,此时,如果参数会改变SQL

的语法结构,执行的结果就会存在问题了,,在登录查询的 SQL ,如果用户名参数值为: ' or 1=1 or ', 此时,填写的密码无论是多少,登录都会成功, 这就是 SQL 注入的问题.

这个问题可以使用预编译语句对象完美解决

  1. 预先发送带有占位符的 SQL 到数据库中进行编译, 语句结构固定下来
  2. 设置参数给对应的占位符,然后再执行 SQL

这里无论是什么参数,都不会再改变 SQL 的语法结构,达到防止 SQL 注入问题的目的

四、查询操作模板代码抽取

/**

  • 处理查询操作
  • <T>:是声明泛型类型
  • List<T>, Class<T>使用声明好的T
  • @param sql 要执行的SQL语句
  • @param type 将每行数据封装的对象类型
  • @param params 执行的SQL需要的参数
  • @return 返回查询到的结果,统一放到List集合中

*/

public static <T> List<T> executeQuery(String sql,Class<T> type, Object...params){

Connection conn = null;

PreparedStatement ps = null;

ResultSet rs = null;

List<T> list = new ArrayList<>();

try {

conn = DruidUtil.getConnection();

ps = conn.prepareStatement(sql);

//为占位符设值

for (int i = 0; i < params.length; i++) { ps.setObject(i + 1, params[i]);

}

rs = ps.executeQuery();

//直到next方法返回false时结束 while (rs.next()) {

//使用反射创建对象

T t = type.newInstance();

//将数据从结果集中获取到,并设置给对象t

//通常情况下,属性名和列名一样,所以我们可以根据属性名去获取对应列的值 

BeanInfo beanInfo = Introspector.getBeanInfo(t.getClass(),

Object.class);

PropertDescriptor[] pds = beanInfo.getPropertyDescriptors();

//操作每个属性

for (PropertyDescriptor pd : pds) {

//获取到属性名

String name = pd.getName();

//根据这个属性名从结果集中获取到数据

Object value = rs.getObject(name);

//获取到属性对应的set方法

Method writeMethod = pd.getWriteMethod(); writeMethod.invoke(t, value);

}

list.add(t);

}

} catch (Exception e) { e.printStackTrace();

} finally {

JdbcUtil.close(conn, ps, rs);

}

return list;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值