文章目录
MVC【横向】
参考动力节点老杜讲的MVC写的笔记,如有错误,还请指正!
不使用mvc模式实现银行转账功能
分析以下AccountTransferServlet他都负责了什么?
- 负责了数据接收
- 负责了核心的业务处理
- 负责了数据库表中数据的CRUD操作(Create【增】 Retrieve【查】 Update【改】 Delete【删】)
- 负责了页面的数据展示
缺点
- 代码的复用性太差。(代码的重用性太差)
- 因为没有进行“职能分工”,没有独立组件的概念,所以没有办法进行代码复用。代码和代码之间的耦合度太高,扩展力太差。
- 耦合度高,导致了代码很难扩展。
- 操作数据库的代码和业务逻辑混杂在一起,很容易出错。编写代码的时候很容易出错,无法专注业务逻辑的编写。
MVC理论基础
-
MVC架构模式【横向】
- M:Model,数据/业务
- pojo、bean、domain
- service
- dao
- V:View,视图/展示
- JSP
- Freemarker
- Velocity
- Thymeleaf
- html
- C:Controller,控制器【核心】
- M:Model,数据/业务
-
对mvc的理解?【面试题】
-
mvc是一种软件架构模式
-
M指Model,模型
完成具体的业务操作,包括处理业务以及处理数据
-
V指View,视图
使用JSP、html完成页面展示
-
C指Controller,是核心控制器
负责获取View的请求,并调用模型将数据交给View展示
-
-
使用mvc架构处理用户请求的具体过程
- 当用户发送请求时,控制器接受到用户的请求后,调用Model处理业务
- Model与数据库相连接,处理完业务后将处理结果和处理完的数据返回给控制器
- 控制器调用View组件进行结果的展示
-
使用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中实现的
- 一般一个业务方法对应一个完整的事务【不绝对】
事务的解决方法
- 在DAO中方法均传入Connection对象,保证Service中和DAO中使用同一个Connection
- 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:异常
待解决
- service方法中的事务控制代码->通过动态代理机制解决
- 没有完全解决对象和对象之间的依赖关系->使用Spring的IoC容器解决