MVC【横向】
参考动力节点老杜讲的MVC写的笔记,如有错误,还请指正!
不使用mvc模式实现银行转账功能
分析以下AccountTransferServlet他都负责了什么?
1.负责了数据接收
2.负责了核心的业务处理
3.负责了数据库表中数据的CRUD操作(Create【增】 Retrieve【查】 Update【改】 Delete【删】)
4.负责了页面的数据展示
缺点
- 代码的复用性太差。(代码的重用性太差)
。因为没有进行“职能分工”,没有独立组件的概念,所以没有办法进行代码复用。代码和代码之间的耦合度太高,扩展力太差。
- 耦合度高,导致了代码很难扩展。
- 操作数据库的代码和业务逻辑混杂在一起,很容易出错。编写代码的时候很容易出错,无法专注业务逻辑的编写。
MVC理论基础
- MVC架构模式【横向】
。M:Model,数据/业务
- pojo、bean、domain
- service
- dao
。V:View,视图/展示
- JSP
- Freemarker
- Velocity
- Thymeleaf
- html
。C:Controller,控制器【核心】
- 对mvc的理解?【面试题】
1.mvc是一种软件架构模式
。M指Model,模型
完成具体的业务操作,包括处理业务以及处理数据
。V指View,视图
使用JSP、html完成页面展示
。C指Controller,是核心控制器
负责获取View的请求,并调用模型将数据交给View展示
2.使用mvc架构处理用户请求的具体过程
。当用户发送请求时,控制器接受到用户的请求后,调用Model处理业务
。Model与数据库相连接,处理完业务后将处理结果和处理完的数据返回给控制器
。控制器调用View组件进行结果的展示
3.使用mvc架构的优点
。降低代码的耦合度
。扩展能力增强
。组件的可复用性增强
Model
dao
- Data Access Object,数据访问对象
- DAO实际上是一种设计模式,属于JavaEE的设计模式之一(不是23种设计模式)
- DAO只负责数据库表的CRUD,没有任何业务逻辑在里面
- 一般情况下,一张表对应一个DAO对象
。比如t_act对应AccoutDao对象
package com.st.bank.dao.impl; import com.st.bank.dao.AccountDao; import com.st.bank.pojo.Account; import com.st.bank.utils.DBUtil; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; /** * @author: TIKI * @Project: mvc -AccountDaoImpl * @Pcakage: com.st.bank.dao.Impl.AccountDaoImpl * @Date: 2022年10月23日 19:27 * @Description:负责account数据的增删改查 */ public class AccountDaoImpl implements AccountDao { /** 插入账户信息 * @param act 账户信息 * @return 1表示插入成功 */ public int insert(Account act){ Connection conn = null; PreparedStatement ps = null; int count = 0; try { conn = DBUtil.getConnection(); String sql = "insert into t_act(actno,balance) values(?,?)"; ps = conn.prepareStatement(sql); ps.setString(1,act.getActno()); ps.setDouble(2,act.getBalance()); count= ps.executeUpdate(); } catch (SQLException e) { throw new RuntimeException(e); }finally { DBUtil.close(null,ps,null); } return count; } /** 根据主键删除账户 * @param id 主键 * @return 删除成功返回1 */ public int deleteById(Long id){ Connection conn = null; PreparedStatement ps = null; int count = 0; try { conn = DBUtil.getConnection(); String sql = "delete from t_act where id = ?"; ps = conn.prepareStatement(sql); ps.setLong(1,id); count = ps.executeUpdate(); } catch (SQLException e) { throw new RuntimeException(e); }finally { DBUtil.close(null,ps,null); } return count; } /** 更新账户 * @param act * @return */ public int update(Account act){ Connection conn = null; PreparedStatement ps = null; int count = 0; try { conn = DBUtil.getConnection(); String sql = "update t_act set balance = ?, actno = ? where id=?"; ps = conn.prepareStatement(sql); ps.setDouble(1,act.getBalance()); ps.setString(2,act.getActno()); ps.setLong(3,act.getId()); count = ps.executeUpdate(); } catch (SQLException e) { throw new RuntimeException(e); }finally { DBUtil.close(null,ps,null); } return count; } /** 根据账号查询账户 * @param actno * @return */ public Account selectByActno(String actno){ Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; Account act = null; try { conn = DBUtil.getConnection(); String sql = "select id, balance from t_act where actno = ?"; ps = conn.prepareStatement(sql); ps.setString(1,actno); rs = ps.executeQuery(); if (rs.next()){ Long id = rs.getLong("id"); Double balance = rs.getDouble("balance"); act = new Account(id,actno,balance); } } catch (SQLException e) { throw new RuntimeException(e); }finally { DBUtil.close(null,ps,rs); } return act; } /** 获取所有的账户 * @return */ public List<Account> selectAll(){ Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; int count = 0; List<Account> list= null; try { conn = DBUtil.getConnection(); String sql = "select id, actno,balance from t_act"; ps = conn.prepareStatement(sql); rs = ps.executeQuery(); while (rs.next()){ Long id = rs.getLong("id"); String actno =rs.getString("actno"); Double balance = rs.getDouble("balance"); list.add(new Account(id,actno,balance)); } } catch (SQLException e) { throw new RuntimeException(e); }finally { DBUtil.close(null,ps,rs); } return list; } }
Pojo、bean、domain
- DAO对象查到的数据如何返回给控制器进而显示到页面?
。利用java语言面向对象的特点->将数据封装成一个对象返回给控制器
- 比如针对t_act设计Account类【pojo对象】
有的人也会把这种专门封装数据的对象,称为【bean对象】,或者称为领域模型对象【domain对象】
package com.st.bank.pojo; /** * @author: TIKI * @Project: mvc -Accout * @Pcakage: com.st.bank.mvc.Accout * @Date: 2022年10月22日 19:21 * @Description:账号实体类 */ public class Account { // 一般属性不设计为基本数据类型,建议使用包装类(引用数据类型),防止查询数据为null带来的问题 private Long id; private String actno; private Double balance; public Account() { } public Account(Long id, String actno, Double balance) { this.id = id; this.actno = actno; this.balance = balance; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getActno() { return actno; } public void setActno(String actno) { this.actno = actno; } public Double getBalance() { return balance; } public void setBalance(Double balance) { this.balance = balance; } }
。一般类的属性不设计为基本数据类型,建议使用包装类(引用数据类型),防止查询数据为null带来的问题
Service
- service中编写纯业务代码,service中的方法名需要体现出处理的是什么业务
。比如针对Account业务,编写AccountService业务
package com.st.bank.service.impl; import com.st.bank.dao.AccountDao; import com.st.bank.dao.impl.AccountDaoImpl; import com.st.bank.exceptions.AppException; import com.st.bank.exceptions.MoneyNotEnoughException; import com.st.bank.pojo.Account; import com.st.bank.service.AccountService; import com.st.bank.utils.DBUtil; import java.sql.Connection; import java.sql.SQLException; /** * @author: TIKI * @Project: mvc -AccountService * @Pcakage: com.st.bank.service.AccountService * @Date: 2022年10月23日 19:29 * @Description: 专门处理Account业务的一个类 * 在该类中应编写纯业务代码 */ public class AccountServiceImpl implements AccountService { // 为什么定义到这里?因为在每一个业务方法中都可以需要连接数据库。 private AccountDao accountDao = new AccountDaoImpl(); // 这里的方法起名,一定要体现出,你要处理的是什么业务。 // 我们要提供一个能够实现转账的业务方法(一个业务对应一个方法。) /** * 完成转账的业务逻辑 * @param fromActno 转出账号 * @param toActno 转入账号 * @param money 转账金额 */ public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, AppException { // service层控制事务 try (Connection connection = DBUtil.getConnection()){ System.out.println(connection); // 开启事务(需要使用Connection对象) connection.setAutoCommit(false); // 查询余额是否充足 Account fromAct = accountDao.selectByActno(fromActno); if (fromAct.getBalance() < money) { throw new MoneyNotEnoughException("对不起,余额不足"); } // 程序到这里说明余额充足 Account toAct = accountDao.selectByActno(toActno); // 修改余额(只是修改了内存中java对象的余额) fromAct.setBalance(fromAct.getBalance() - money); toAct.setBalance(toAct.getBalance() + money); // 更新数据库中的余额 int count = accountDao.update(fromAct); // 模拟异常 /*String s = null; s.toString();*/ count += accountDao.update(toAct); if (count != 2) { throw new AppException("账户转账异常!!!"); } // 提交事务 connection.commit(); } catch (SQLException e) { throw new AppException("账户转账异常!!!"); } } }
- 在service中调用dao和view
- 事务一定是在service中实现的
。一般一个业务方法对应一个完整的事务【不绝对】
事务的解决方法
1.在DAO中方法均传入Connection对象,保证Service中和DAO中使用同一个Connection
2.service调用DAO中的方法时,属于同一个线程,使用Map集合通过线程对象绑定Connection对象
。ThreadLocal:实际上是一下Map集合
简单的ThreadLoacl
package com.powernode.threadlocal; import java.util.HashMap; import java.util.Map; /** * 自定义一个ThreadLocal类 */ public class MyThreadLocal<T> { /** * 所有需要和当前线程绑定的数据要放到这个容器当中 */ private Map<Thread, T> map = new HashMap<>(); /** * 向ThreadLocal中绑定数据 */ public void set(T obj){ map.put(Thread.currentThread(), obj); } /** * 从ThreadLocal中获取数据 * @return */ public T get(){ return map.get(Thread.currentThread()); } /** * 移除ThreadLocal当中的数据 */ public void remove(){ map.remove(Thread.currentThread()); } }
使用ThreadLocal的DButil
package com.st.bank.utils; import java.sql.*; import java.util.ResourceBundle; /** * @author: TIKI * @Project: mvc -Dbutil * @Pcakage: com.st.bank.utils.DBUtil * @Date: 2022年10月22日 19:04 * @Description:数据库工具类 */ public class DBUtil { private static ResourceBundle bundle = ResourceBundle.getBundle("resources/jdbc"); private static String driver = bundle.getString("driver"); private static String url = bundle.getString("url"); private static String user = bundle.getString("user"); private static String password = bundle.getString("password"); // 不让创建对象,因为工具类中的方法都是静态的。不需要创建对象。 // 为了防止创建对象,故将构造方法私有化。 private DBUtil(){} // DBUtil类加载时注册驱动 static { try { Class.forName(driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } // 这个对象实际上在服务器中只有一个。 private static ThreadLocal<Connection> local = new ThreadLocal<>(); /** * 这里没有使用数据库连接池,直接创建连接对象。 * @return 连接对象 * @throws SQLException */ public static Connection getConnection() throws SQLException { Connection conn = local.get(); if (conn == null) { conn = DriverManager.getConnection(url, user, password); local.set(conn); } return conn; } /** * 关闭资源 * @param conn 连接对象 * @param stmt 数据库操作对象 * @param rs 结果集对象 */ public static void close(Connection conn, Statement stmt, ResultSet rs){ if (rs != null) { try { rs.close(); } catch (SQLException e) { throw new RuntimeException(e); } } if (stmt != null) { try { stmt.close(); } catch (SQLException e) { throw new RuntimeException(e); } } if (conn != null) { try { conn.close(); // 思考一下:为什么conn关闭之后,这里要从大Map中移除呢? // 根本原因是:Tomcat服务器是支持线程池的。也就是说一个人用过了t1线程,t1线程还有可能被其他用户使用。 local.remove(); } catch (SQLException e) { throw new RuntimeException(e); } } } }
- 思考一下:为什么conn关闭之后,这里要从大Map中移除呢?
根本原因是:Tomcat服务器是支持线程池的。也就是说一个人用过了t1线程,t1线程还有可能被其他用户使用。
三层架构【纵向】
三层架构是指表示层、业务逻辑层和持久化层
- 表示层/表现层/web层(UI)
直接与前端交互
。接受前端ajax请求
。返回json数据给前端
- 业务逻辑层Service(BLL)
。处理表现层转发过来的前端请求(具体业务)
。将从持久层获取的数据返回到表现层
- 持久化层Dao(数据访问层)
直接操作数据库完成CRUD,并将获得的数据返回到上一层,即业务逻辑层
。相关技术
- JDBC
- MyBatis
- Hibernate(实现了JPA规范)
配置较复杂、效率较低
- JPA
- SpringData(实现了JPA规范)
- ActiveJDBC
mvc和三层架构之间的关系
- 三层架构中的表示层即为mvc架构中的View和Controller
- 三层架构中的业务逻辑层即为mvc架构中Model中的Service
- 三层架构中的持久化层即为mvc架构中Model中的DAO
SSM与三层架构的关系
- SSM框架包括Spring、 SpringMVC、MyBatis
。Spring
Spring负责管理整个项目,负责整个项目所有对象的创建、初始化、销毁以及维护对象和对象之间的关系。
- Spring并不属于三层架构中的任何一层
。SpringMVC(搭建了MVC架构模式)
完成了三层架构中的表示层
- 作为 View 层的实现者,完成用户的请求接收功能
- SpringMVC 的 Controller作为整个应用的控制器,完成用户请求的转发及对用户的响应
- 并提供了业务逻辑层的接口
。MyBatis
作为持久化层的实现者,完成对数据库的增、删、改、查功能
最终实现
不同功能的类放在不同的包下
层和层之间通过接口联系
- pojo:实体类[M]
- dao:数据库表(持久化层)[M]
。AccountDao【接口】
。impl
- AccountDaoImpl【实现类】
- service:纯业务代码(业务逻辑层)[M]
。AccountService【接口】
。impl
- AccountServiceImpl【实现类】
- web:Controller+view(表现层)[V][C]
- utils:工具类
- exceptions:异常
待解决
1.service方法中的事务控制代码->通过动态代理机制解决
2.没有完全解决对象和对象之间的依赖关系->使用Spring的IoC容器解决