分层和事务
一个项目中,为了代码好维护,各部分解耦程度高,将各功能分开。例如:
jsp 的一种设计模式:
jsp:展示数据
JavaBean:和数据打交道(与数据库的交互)
servlet: 接受请求,处理业务逻辑
一:MVC思想(分层)★★★
将业务逻辑,代码,展示分开的一种思想,体现更低的耦合度
M: model 模型。主要是封装数据,封装对数据的访问
V: view 试图。主要是用来展示数据,一般就是jsp 担任
C: ctrl 控制。接受请求,找到相应的javabean 完成业务逻辑
jsp设计模式一
model1 了解:jsp+javabean
实现在一个jsp 页面上用表单提交数据,到另一个jsp 页面。并且在那个页面上,将数据读取出来;
首先要写一个符合Javabean 规范的数据封装类:User
(数据接收和获取的代码)
将获取到的数据封装成一个对象,使用的是反射技术。
二:反射技术:
- 获取class 对象
1. 方式一:Class class = Class.forName(“一个类的全限定名”)
2. 方式二:Class class = 对象.getClass()
3. 方式三:Class class=类名.class; - 获取class 对象之后,利用class对象。获取类的一般方法,构造器创建对象
- 创建对象:Constructor con = class(Class 对象).getConstructor();
类名 demo = (类名)con.newInstance();传入参数就是调用有参的构造器,不传入参数就是无参的构造器。
class.newINstance(); - 通过获取的class 对象,获取类中的所有字段
1. 字段:getFiled
2. 获取公共方法并且运行: Menthod m = class.getMethod();
m.invoke(创建的对象);
3. 获取任何修饰的方法:class.getDeclarMethod(方法名);
若是私有的方法,必须要让 要让这个方法能访问:设置m.setAccessible(true):反射时取消Java的访问限制
此时m.invoke(对象) 就可以运行了
4. 如果是要获取有参数的方法:getDeclarMethod(方法名,参数类型 .class)
在运行时传入参数值:m.invoke(class.newInstance()/对象,参数)
总之就是,invoke 方法是有返回值的,返回值就是目标函数对象的返回值
- 创建对象:Constructor con = class(Class 对象).getConstructor();
总结就是:有了class 对象就是无所不能
jsp 设计模式二/mode2
jsp + javabean + servlet
主要使用的就是javautils
javaUtils :可以看作封装数据的一个工具类
使用步骤:
3. 导入jar 包
4. 使用BeanUtils.populate(Object bean,Map map); //这个方法其实底层使用的就是反射技术(有对象就能获取class 对象,就能获取类中的所有字段和方法)
设计模式二,就是MVC的一个体现
在Javaweb 的开发中,MVC的体现就是javaee 的三层架构
三:javaee 的三层架构:
Javaee 的三层架构是MVC思想在就Java中的体现。其实就是分包。
程序设计有一个要求:低耦合。将程序模块之间尽量的解耦★★★
- web 层:
- 展示数据(jsp)展示数据(jsp),调用servlet
- servlet : 接收请求,找到相应的service ,调转方法 完成逻辑操作。将信息生成或者是页面跳转。
- service : 业务层(★★★★)
- 完成业务操作,调用dao
- dao(data access objeact 数据访问对象)
- 对数据库完成curd 操作
以存转账为例子:
在三层架构中,最复杂和重要的就是service 层:到底要如何完成这件事
四:事务:
事务:就是一个完整的事情,包含操作单元(一个或多个),
特
点
就
是
:
要
么
都
成
功
,
要
么
都
失
败
。
特点就是: 要么都成功,要么都失败。
特点就是:要么都成功,要么都失败。
[例如:转账,转账方转账之后停电,那么这个事务,要么成功:成功转账并且收款方收到了钱。要么失败:转账方转账失败并且收款方没有收到转账]
mysql 中的事务:
在mysql 中事务是默认自动提交的,一条sql 语句就是一个事务。
如何开启手动事务:
- 关闭自动事务:set autocommit=off;每一条语句都要手动提交:commit;
- 手动开启一个事务【sql 语句】
- start transaction :开启一个事务
- commit : 提交一个事务
- rollback : 事务回滚(取消进行的操作,返回到开启事务之前)
Java中的事务:★★★
接口Connection 中的方法:
- setAutoCommit(false):手动开启一个事务
- rollback():回滚
- commit() : 提交事务
扩展:还原点
void rollback(Saepoint savepoint) ;回滚还原到什么地方
实现案例:转账
要保证在这个事务中,转出者的减少和转入者的增多,这两个操作不能只完成一个。无论是外部的因素还是程序本身的异常,都要保证一些列操作要么都成功要么都失败。
- 开启事务:在service 中,但是在dao 中要使用connection
★★★方法1.:在service 中获取连接,开启事务。为了在dao 中能使用连接:
向下传递参数:connetion。 注意一点连接在哪里获取就要在哪里释放
<AccountService 中的代码>
Connection conn= null;
try {
conn = jdbcUtils.getConn();
//开启事务
conn.setAutoCommit(false);
//转出
account.accountOut(conn,fromName,money);
int i=1/0;//数据异常
//转入
account.accountIn(conn,toNAme,money);
//事务提交
conn.commit();
//释放资源
jdbcUtils.closeConn(conn);
} catch (Exception e) {
e.printStackTrace();
conn.rollback();//出现异常就回滚
jdbcUtils.closeConn(conn);
throw e;
}
- 事务回滚:一旦出现异常就回滚
- 提交事务:在service 中,事务都完成了就提交
★★★★方法二 :在service 中获取连接再传到dao 中可能会导致不安全。可以将connection 对象绑定到当前线程上(每一次请求servlet都会开启一个线程)。这样在service 中获取连接,绑定到当前线程上,在dao 中就可以直接获取了。
【要想保证是同一个事务就要保证是同一个连接】
类ThreadLocal 可以将某个状态与某个线程相关联
构造器: new ThreadLocal();
1.void set(T value); 将内容和当前内容绑定
2.Object get(); 获取和当前线程绑定的内容
3.remove(); 将当前线程和内容解绑
其实在内部维护了一个map 集合:
set 相当于: map.set(当前线程,value)
get 相当于:map.get(当前线程)
remove 相当于:map.remove(当前线程)
方法二很重要的实现就是改变了工具类,将连接的获取,连接和当前线程的绑定,开启事务,事务的提交,事务回滚,资源的释放,相应的方法全部都写在工具类中封装成静态方法
工具类的代码:
package z_Utils;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
//获取与数据库连接的工具类。将连接绑定在当前线程上
public class DateSourceUtils {
private static ComboPooledDataSource ds = new ComboPooledDataSource();
//线程的局部变量:使用private static 修饰
private static ThreadLocal<Connection> tl = new ThreadLocal<>();
//获取连接池
public static DataSource getDataSource() {
return ds;
}
/**
* 从当前线程获取连接
*如果是第一次就将连接绑定在当前线程上
* @return
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
//从当前线程获取连接
Connection conn = tl.get();
if (conn == null) {
conn = ds.getConnection();
//并且和当前线程绑定
tl.set(conn);
}
return conn;
}
//释放资源
public static void closeConn(Connection conn) {
//资源关闭时,还应该解绑
if(conn!=null){
try{
//和当前线程解绑
tl.remove();
conn.close();
}catch (Exception e){
}
conn=null;//无论conn 是否释放,都将conn 设为null;空对象会被Java垃圾回收机制快速回收
}
}
public static void closeStatement(Statement st) {
if(st!=null){
try{
st.close();
}catch(Exception e){
}
st=null;
}
}
public static void closeResult(ResultSet rs){
if(rs!=null){
try{
rs.close();
}catch(Exception e){
}
rs=null;
}
}
//释放资源
public static void closeResource(Connection conn,Statement st,ResultSet rs){
closePreAndRe(st,rs);
closeStatement(st);
}
//释放两个连接:当开启事务时,connection 会在事务提交或者是回滚的时候释放
public static void closePreAndRe(Statement st,ResultSet rs){
closeResult(rs);
closeStatement(st);
}
/**
* 开启事务
* @throws SQLException
*/
public static void startTransaction() throws SQLException {
//从线程中获取连接,开启事务
getConnection().setAutoCommit(false);
}
/**
* 提交事务/解除绑定/关闭资源
* @throws SQLException
*/
public static void commitAndClose() throws SQLException {
//获取连接
Connection conn= null;
try {
conn = getConnection();
//提交事务
conn.commit();
//解除绑定,释放资源
closeConn(conn);
} catch (SQLException e) {
e.printStackTrace();
//解除绑定/释放资源
tl.remove();
conn.close();
throw e;
}
}
/**
* 事务回滚
* @throws SQLException
*/
public static void rollbackAndClose() throws SQLException {
//获取连接
Connection conn= null;
try {
conn = getConnection();
//提交事务
conn.rollback();
//解除绑定释放资源
closeConn(conn);
} catch (SQLException e) {
e.printStackTrace();
//解除绑定/释放资源
tl.remove();
conn.close();
throw e;
}
}
}
以上的处理方式都是使用jdbc ,和自己写的工具类,无论是jdbcUtils 还是DateSourceUtils
使用jdbc 的框架:DButils ★★★★(使用 )
使用复习:
创建:QueryRunner
编写sql
执行
QueryRunner:
- new QueryRunner(DateSource ds): 这是自动事务(一行sql 一个事务)
传给它一个连接池,它会自动的去连接池中获取一个连接,资源的释放和数据的提交都是自动的 - new QueryRunner() : 手动事务
事务要自动提交,并且资源也要自己来释放
常用方法:
1. update(Connection conn,String sql ,Object …params);在语句执行时将连接传入,执行的时cud 操作。一般查询操作是不用使用事务的。
【其中的参数Connection conn 是工具类DateSourceUtils 中从线程中获取的连接】
2. query 查询
使用:在Service 中,获取连接,开启事务,提交事务,释放资源,处理回滚。在DAO 中的:
//创建QueryRunner
QueryRunner pr = new QueryRunner();//手动事务
//编写sql 语句
String sql = "update account set money = money - ? where aname = ?";
//执行sql 语句
Connection conn = DateSourceUtils.getConnection();
int i = pr.update(conn,sql,money,fromName);//因为是手动事务,所以不会帮我们关闭连接资源。可以查看源码
//自动事务会帮我们自动的释放资源
关于事务的总结:★★★
事务的特性:
原子性:事务不可分割。要么全部成功,要么全部失败
一致性:事务执行前后,业务的状态要保持一致:比如存款和借款,事务的前后银行中的钱是不变的
隔离性:一个事务执行的时候最好不要受到其他事务的影响
持久性: 一旦事务提交或者是回滚。这个状态都要持久化到数据库中
如果不考虑隔离性会出现的问题:
- 脏读:在一个事务中读取到另一个事务没有提交的事务
- 不可重复读:在一个事务中,查两次的结果是一样的(针对update)
- 虚读:在一个事务中,两次查询的结果是不一致的(针对insert)
出现以上问题的解决方式:
设置数据库的隔离级别
- read uncommited 读未提交 【以上三个问题都会发生】
- read commit 读已提交 【避免脏读】★
- repeatable read 可重复读取【 避免脏读和重复读】★
- serializable 串行化【可以避免所有问题】
安全性是由小到大,但是效率是由大到小
在开发中是绝对不能允许脏读的
mysql 中的默认级别是:repeatable read
查看数据库的默认隔离级别:sql 语句:select @@tx_isolation
在Java中控制隔离级别:Connection 中的方法,void setTransactionIsolation(int level)
level 是表示各个级别的常量
操作涉及到数据库时,还有事务时,工具类是一个非常重要的东西,不仅仅在写代码的时候使用更加重要的是这是一种思想的体现