MVC/事务

分层和事务

一个项目中,为了代码好维护,各部分解耦程度高,将各功能分开。例如:
jsp 的一种设计模式:
jsp:展示数据
JavaBean:和数据打交道(与数据库的交互)
servlet: 接受请求,处理业务逻辑

一:MVC思想(分层)★★★

将业务逻辑,代码,展示分开的一种思想,体现更低的耦合度
M: model 模型。主要是封装数据,封装对数据的访问
V: view 试图。主要是用来展示数据,一般就是jsp 担任
C: ctrl 控制。接受请求,找到相应的javabean 完成业务逻辑

jsp设计模式一
model1 了解:jsp+javabean
实现在一个jsp 页面上用表单提交数据,到另一个jsp 页面。并且在那个页面上,将数据读取出来;
首先要写一个符合Javabean 规范的数据封装类:User
(数据接收和获取的代码)
在这里插入图片描述
将获取到的数据封装成一个对象,使用的是反射技术。

二:反射技术:

  1. 获取class 对象
    1. 方式一:Class class = Class.forName(“一个类的全限定名”)
    2. 方式二:Class class = 对象.getClass()
    3. 方式三:Class class=类名.class;
  2. 获取class 对象之后,利用class对象。获取类的一般方法,构造器创建对象
    1. 创建对象:Constructor con = class(Class 对象).getConstructor();
      类名 demo = (类名)con.newInstance();传入参数就是调用有参的构造器,不传入参数就是无参的构造器。
      class.newINstance();
    2. 通过获取的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 方法是有返回值的,返回值就是目标函数对象的返回值

总结就是:有了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中的体现。其实就是分包。
程序设计有一个要求:低耦合。将程序模块之间尽量的解耦★★★

  1. web 层:
  • 展示数据(jsp)展示数据(jsp),调用servlet
  • servlet : 接收请求,找到相应的service ,调转方法 完成逻辑操作。将信息生成或者是页面跳转。
  1. service : 业务层(★★★★)
  • 完成业务操作,调用dao
  1. dao(data access objeact 数据访问对象)
  • 对数据库完成curd 操作
    以存转账为例子:
    在这里插入图片描述

在三层架构中,最复杂和重要的就是service 层:到底要如何完成这件事

四:事务:

事务:就是一个完整的事情,包含操作单元(一个或多个), 特 点 就 是 : 要 么 都 成 功 , 要 么 都 失 败 。 特点就是: 要么都成功,要么都失败。
[例如:转账,转账方转账之后停电,那么这个事务,要么成功:成功转账并且收款方收到了钱。要么失败:转账方转账失败并且收款方没有收到转账]
mysql 中的事务:
在mysql 中事务是默认自动提交的,一条sql 语句就是一个事务。
如何开启手动事务:

  1. 关闭自动事务:set autocommit=off;每一条语句都要手动提交:commit;
  2. 手动开启一个事务【sql 语句】
    - start transaction :开启一个事务
    - commit : 提交一个事务
    - rollback : 事务回滚(取消进行的操作,返回到开启事务之前)

Java中的事务:★★★

接口Connection 中的方法:

  1. setAutoCommit(false):手动开启一个事务
  2. rollback():回滚
  3. commit() : 提交事务

扩展:还原点
void rollback(Saepoint savepoint) ;回滚还原到什么地方


实现案例:转账
要保证在这个事务中,转出者的减少和转入者的增多,这两个操作不能只完成一个。无论是外部的因素还是程序本身的异常,都要保证一些列操作要么都成功要么都失败。

  1. 开启事务:在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;
            }
  1. 事务回滚:一旦出现异常就回滚
  2. 提交事务:在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:

  1. new QueryRunner(DateSource ds): 这是自动事务(一行sql 一个事务)
    传给它一个连接池,它会自动的去连接池中获取一个连接,资源的释放和数据的提交都是自动的
  2. 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);//因为是手动事务,所以不会帮我们关闭连接资源。可以查看源码
        //自动事务会帮我们自动的释放资源


关于事务的总结:★★★

事务的特性:
原子性:事务不可分割。要么全部成功,要么全部失败
一致性:事务执行前后,业务的状态要保持一致:比如存款和借款,事务的前后银行中的钱是不变的
隔离性:一个事务执行的时候最好不要受到其他事务的影响
持久性: 一旦事务提交或者是回滚。这个状态都要持久化到数据库中

如果不考虑隔离性会出现的问题:

  1. 脏读:在一个事务中读取到另一个事务没有提交的事务
  2. 不可重复读:在一个事务中,查两次的结果是一样的(针对update)
  3. 虚读:在一个事务中,两次查询的结果是不一致的(针对insert)

出现以上问题的解决方式:
设置数据库的隔离级别

  • read uncommited 读未提交 【以上三个问题都会发生】
  • read commit 读已提交 【避免脏读】★
  • repeatable read 可重复读取【 避免脏读和重复读】★
  • serializable 串行化【可以避免所有问题】
    安全性是由小到大,但是效率是由大到小
    在开发中是绝对不能允许脏读的
    mysql 中的默认级别是:repeatable read
    查看数据库的默认隔离级别:sql 语句:select @@tx_isolation
    在Java中控制隔离级别:Connection 中的方法,void setTransactionIsolation(int level)
    level 是表示各个级别的常量

   操作涉及到数据库时,还有事务时,工具类是一个非常重要的东西,不仅仅在写代码的时候使用更加重要的是这是一种思想的体现  
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值