MVC模式
MVC:Model、View、Controller
MVC设计模式的引入
在我们实际开发的最后到产品上线,供给客户使用,客户通过浏览器或者app等进行数据的操作,实现这个的有,处理发送请求,业务逻辑处理以及访问数据库,这三个功能我们是可以放到一块使用的,但是如果放在一起,代码便会很臃肿,不利于维护,于是便出现了代码分层思想,把代码按照功能分为三层,即模型层(Model)、显示层(View)、控制层(Controller),这种代码的组织架构就叫MVC模式
MVC分层图
模型层(Model):在模型层中又可以分为两层,即Service层和Dao层,这两层的主要功能是:
Service层:负责一些业务处理,比如说:获取数据库连接,关闭数据库连接,事务回滚或者一些复杂的逻辑业务处理
Dao层:(Database Accept Object) 负责访问数据库,对数据的操作,获取结果集,将结果集中的数据装到OV(Object Value)对象中,之后再返回给Service层
Controller层:主要功能是处理用户的请求
View层:主要负责显示数据(Html、Css、jQuery等等)
实现分层的组织包结构图
代码的调用顺序:
View>Controller>Service>Dao,如果上层代码对下层代码的依赖程度过高,就需要对每层的代码定义一个(标准)接口。
定义接口之后的包结构
Model层的Dao层设计思想:
为数据库中的emp表设计数据操作的Dao表,在实际开发过程中,Dao层需要先定义出自己的标准(接口),降低耦合度
1、新建一个项目
2、导入相关的开发包,比如驱动包(oracle,mysql,sql server等的驱动包)
3、构造出包的结构
4、创建emp对象,放在vo包下面,emp表中的字段名对应设置成javabean中的成员变量
package com.zhouym.mvcpro.vo;
import java.io.Serializable;
import java.util.Date;
@SuppressWarnings("serial")
public class Emp implements Serializable{
//对应EMP表中的字段名
private Integer empno;
private String ename;
private String job;
private Integer mgr;
private Date hiredate;
private Double sal;
private Double comm;
private Integer deptno;
public Emp() {
super();
}
public Emp(Integer empno, String ename, String job, Integer mgr, Date hiredate, Double sal, Double comm,
Integer deptno) {
super();
this.empno = empno;
this.ename = ename;
this.job = job;
this.mgr = mgr;
this.hiredate = hiredate;
this.sal = sal;
this.comm = comm;
this.deptno = deptno;
}
public Integer getEmpno() {
return empno;
}
public void setEmpno(Integer empno) {
this.empno = empno;
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
public Integer getMgr() {
return mgr;
}
public void setMgr(Integer mgr) {
this.mgr = mgr;
}
public Date getHiredate() {
return hiredate;
}
public void setHiredate(Date hiredate) {
this.hiredate = hiredate;
}
public Double getSal() {
return sal;
}
public void setSal(Double sal) {
this.sal = sal;
}
public Double getComm() {
return comm;
}
public void setComm(Double comm) {
this.comm = comm;
}
public Integer getDeptno() {
return deptno;
}
public void setDeptno(Integer deptno) {
this.deptno = deptno;
}
@Override
public String toString() {
return "Emp [empno=" + empno + ", ename=" + ename + ", job=" + job + ", mgr=" + mgr + ", hiredate=" + hiredate
+ ", sal=" + sal + ", comm=" + comm + ", deptno=" + deptno + "]";
}
}
注意的是:
1、简单的java类中,最好使用基本类型对应的包装类
2、实现Serializable接口,方便以后程序的拓展(比如说序列化该类对象就需要实现该接口)
5、开发Dao层的接口
定义一个父接口,定义一个适配器类,再定义子接口,让适配器类实现父接口,让一个子接口或者多个子接口继承父接口,再让一个真正的实现类去继承适配器类并且实现这些子接口,这样的好处是:可以让这个真正的实现类选择性的重写方法,根据实际需要选择要重写的方法,而不是被迫的全部重写这些方法。
父接口
package com.zhouym.mvcpro.dao;
import java.util.List;
import java.util.Set;
/**
* @author zhouym
*
* @param <K> 在对应实体类中表示数据库主键值的字段类型
* @param <V> 表示对应的实体类的类型
*/
public interface IBaseDAO<K,V> {
/**
* 增加雇员数据的方法
* @param emp 保存要插入数据的对象
* @return
* @throws Exception
*/
public int insert(V v) throws Exception;
/**
* 修改数据
* @param emp 保存要插入数据的对象
* @return
* @throws Exception
*/
public int update(V v) throws Exception;
/**
* 根据编号删除数据
* @param K
* @return
* @throws Exception
*/
public int deleteById(K k) throws Exception;
/**
* 批量删除数据
* @param k
* @return
* @throws Exception
*/
public int deleteBatch(Set<K> ids) throws Exception;
/**
* @param K 根据编号查询数据
* @return
* @throws Exception
*/
public V selectById(K K) throws Exception;
/**
* 模糊查询
* @param kw 查询的内容
* @param cp 表示当前页显示的数据量
* @param ls 表示当前页
* @return
* @throws Exception
*/
public List<V> selectSplitAll(String kw,Integer cp,Integer ls) throws Exception;
/**
* 统计数据量
* @param kw 表示根据指定的参数显示统计量
* @return
* @throws Exception
*/
public int selectCount(String kw) throws Exception;
}
让适配器类实现父接口,子接口继承父接口
适配器类
package com.zhouym.mvcpro.adapter;
import java.util.List;
import java.util.Set;
import com.zhouym.mvcpro.dao.IBaseDAO;
//适配器类实现父接口
public class DAOAdapter<K,V> implements IBaseDAO<K, V>{
@Override
public int insert(V v) throws Exception {
return 0;
}
@Override
public int update(V v) throws Exception {
return 0;
}
@Override
public int deleteById(K k) throws Exception {
return 0;
}
@Override
public int deleteBatch(Set<K> ids) throws Exception {
return 0;
}
@Override
public V selectById(K K) throws Exception {
return null;
}
@Override
public List<V> selectSplitAll(String kw, Integer cp, Integer ls) throws Exception {
return null;
}
@Override
public int selectCount(String kw) throws Exception {
return 0;
}
}
子接口
在子接口中,可以定义本接口特有的方法,也可以不写,默认继承了父类的所有方法
package com.zhouym.mvcpro.dao;
import com.zhouym.mvcpro.vo.Emp;
/**
* 继承了父接口的所有方法(继承的是公共的方法)
* @author zhouym
*可以在本接口中定义特有的方法
*/
public interface IEmpDAO extends IBaseDAO<Integer,Emp>{
}
真正的实现类
package com.zhouym.mvcpro.dao.impl;
import java.sql.Connection;
import java.util.List;
import java.util.Set;
import com.zhouym.mvcpro.vo.DBUtil;
import com.zhouym.mvcpro.adapter.DAOAdapter;
import com.zhouym.mvcpro.dao.IEmpDAO;
import com.zhouym.mvcpro.vo.Emp;
/**
* 通过适配器模式,继承适配器类,实现接口,可便于在本类中选择性的继承需要用到的方法
* @author
*
*/
public class EmpDAOImpl extends DAOAdapter<Integer, Emp> implements IEmpDAO {
//将数据库连接对象传进来
private Connection conn;
//定义无参构造以及带参构造
public EmpDAOImpl() {
super();
}
public EmpDAOImpl(Connection conn) {
super();
this.conn = conn;
}
@Override
public int insert(Emp vo) throws Exception {
String sql = "insert into emp(ename,job,sal,hiredate,mgr,comm,deptno) values(?,?,?,?,?,?,?)";
System.out.println(sql);
return DBUtil.save(conn, sql, vo, false);
}
@Override
public int update(Emp vo) throws Exception {
String sql = "update emp set mgr=? , hiredate=? where ename=?";
System.out.println(sql);
return DBUtil.edit(conn, sql, vo);
}
@Override
public int deleteById(Integer empno) throws Exception {
String sql="delete from emp where empno=?";
return DBUtil.remove(conn, sql, empno);
}
//不需要的方法可以不用重写
@Override
public int deleteBatch(Set<Integer> ids) throws Exception {
StringBuilder sql = new StringBuilder("delete from emp where empno in (");
return DBUtil.remove(conn, sql, ids);
}
@Override
public Emp selectById(Integer id) throws Exception {
String sql = "select empno,ename,job,sal,hiredate,mgr,comm,deptno from emp where empno=?";
return DBUtil.selectOne(conn, sql, Emp.class, id);
}
@Override
public List<Emp> selectSplitAll(String kw, Integer cp, Integer ls) throws Exception {
return DBUtil.selectSplitAll("A", 1, 3);
}
@Override
public int selectCount(String kw) throws Exception {
String sql = "select count(*) from emp where ename like ?";
return DBUtil.selectCount(conn, sql, "%"+kw+"%");
}
}
最后在这个实现类中的方法内调用工具类中的方法
package com.zhouym.mvcpro.vo;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
public class DBUtil {
private static final int[] RETURN_GENERATED_KEYS = null;
private static PreparedStatement pst;
private static ResultSet rst;
/**
* 实现数据的增加
*
* @param conn
* 连接对象名
* @param sql
* sql语句对象
* @param vo
* Volue Object
* @param getAutoKey
* 是否需要主动增长的主键值,如果getAutoKey为True,则需要返回自动增长的主键值
* @return 返回收到影响的行数
* @throws Exception
*/
public static <T> int save(Connection conn, String sql, T vo, boolean getAutoKey) throws Exception {
pst = conn.prepareStatement(sql,RETURN_GENERATED_KEYS);
// 为sql中的占位符“?”赋值
// sql = "insert into emp(ename,job,sal,hiredate,mgr,comm,deptno)
// values(?,?,?,?,?,?,?)"
// 分割后:ename,job,sal,hiredate,mgr,comm,deptno
String[] columns = sql.split("\\(")[1].split("\\)")[0].split(",");
// 取得vo类对应的class类对象 vo=emp
Class<?> cls = vo.getClass();
// 遍历字符串数组
for (int i = 0; i < columns.length; i++) {
// 获取当前字段对应在vo类中属性的值,调用对应的getter方法获取
Method m = cls.getDeclaredMethod("get" + upperCase(columns[i]));
// 调用方法取得值
Object fvalue = m.invoke(vo);
// 将这个值作为占位符的内容
pst.setObject(i + 1, fvalue);
}
// 对关键字段的自增长的判断
if (getAutoKey) {
rst = pst.getGeneratedKeys();
if (rst.next()) {
return rst.getInt(1);
}
} else {
return pst.executeUpdate();
}
return 0;
}
public static <T> int edit(Connection conn, String sql, T vo) throws Exception {
PreparedStatement pst = conn.prepareStatement(sql);
String[] columns = sql.split("set")[1].split("where")[0].split(",");
// 获取类对象
Class<?> cls = vo.getClass();
// 遍历数组
for (int i = 0; i < columns.length; i++) {
// 获取字段名
String fieldName = columns[i].split("=")[0].trim();
// 获取cls类中的字段方法
Method m = cls.getDeclaredMethod("get" + upperCase(fieldName));
// 获取字段值
Object fvalue = m.invoke(vo);
pst.setObject(i + 1, fvalue);
}
// 分割判断条件的字符,获取判断条件字段名
String conditionFieldName = sql.split("where")[1].split("=")[0].trim();
Object ob = cls.getDeclaredMethod("get" + upperCase(conditionFieldName)).invoke(vo);
pst.setObject(columns.length + 1, ob);
return pst.executeUpdate();
}
public static int remove(Connection conn, String sql, Object... params) throws SQLException {
pst = conn.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
pst.setObject(i + 1, params[i]);
}
return pst.executeUpdate();
}
public static int remove(Connection conn, StringBuilder sql, Set<Integer> ids) throws Exception {
Iterator<Integer> iter = ids.iterator();
// 遍历迭代器
while (iter.hasNext()) {
sql.append(iter.next() + ",");
}
sql.delete(sql.length() - 1, sql.length());
sql.append(")");
PreparedStatement pst = conn.prepareStatement(sql.toString());
return pst.executeUpdate();
}
public static <T> T selectOne(Connection conn, String sql, Class<T> clz, Object... params) throws Exception {
pst = conn.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
pst.setObject(i + 1, params[i]);
}
ResultSet rst = pst.executeQuery();
T t = null;
if (rst.next()) {
t = clz.newInstance();
// 获取所有属性
Field[] fs = clz.getDeclaredFields();
for (int i = 0; i < fs.length; i++) {
// 获取属性名
String fname = fs[i].getName();
// 获取属性对应的方法名
Method m = clz.getDeclaredMethod("get" + upperCase(fname));
// 根据属性名从结果集中获取数据
Object fvalue = null;
try {
fvalue = rst.getObject(fname);
} catch (Exception e) {
}
m.invoke(t, fvalue);
}
}
return t;
}
public static List<Emp> selectSplitAll(String kw, Integer cp, Integer ls) throws SQLException {
List<Emp> list = new ArrayList<Emp>();
// 对kw作空值判断
if (kw == null) {
kw = "";
}
kw = "%" + kw + "%";
// 创键数据路连接对象
Connection conn = ConnPoolUtil.getConnection();
// 封装sql语句
String sql = "select empno,ename,job,hiredate,sal,comm,mgr,deptno from emp" + " where ename like ? limit ?,?";
// 创建sql发送对象
PreparedStatement pst = conn.prepareStatement(sql);
// 为占位符设置内容
pst.setObject(1, kw);
pst.setObject(2, (cp - 1) * ls);
pst.setObject(3, ls);
// 执行sql语句,获取的数据会存在resultSet中,一种数据表的结构,存在指针,初始位置在第一行数据上面
ResultSet rst = pst.executeQuery();
// 通过循环,使用next()方法将指针下移
while (rst.next()) {
list.add(new Emp(rst.getInt("empno"), rst.getString("ename"), rst.getString("job"), rst.getInt("mgr"),
rst.getDate("hiredate"), rst.getDouble("sal"), rst.getDouble("comm"), rst.getInt("deptno")));
}
rst.close();
pst.close();
ConnPoolUtil.close(conn);
return list;
}
public static int selectCount(Connection conn, String sql, Object... params) throws Exception {
pst = conn.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
pst.setObject(i + 1, params[i]);
}
ResultSet rst = pst.executeQuery();
if (rst.next()) {
return rst.getInt(1);
}
return 0;
}
public static String upperCase(String column) {
// 方法名第一个首字母大写+后面的字母
return column.substring(0, 1).toUpperCase() + column.substring(1, column.length());
}
}
数据库连接池
数据库连接池原理:
1.当系统启动【初始化的时候】会创建5【可设置】个数据库初始连接
2.当客户请求到来的时候【用户要获取Connection对象的时候】
2.1连接池中有空闲连接,直接将空闲的连接中的一个赋给客户使用
2.2如果连接池中没有空闲连接了,而且连接的数据量没有超过最大数量,
那么连接池会向数据库申请创建新的连接【5 可设置】,然后交给客户使用
2.3在2.2的情况下超过了最大连接数据,等待
3.当客户数据库操作完成后,这个连接怎么处理
3.1连接池中的空闲连接数已经超过了初始连接数,那么直接销毁
3.2连接池中的空闲连接数没有超过初始连接数据,那么进入空闲状态
3.3如果有等待的用户,那么这个连接直接交给等待的用户使用
数据库连接池的概念:
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。
使用数据库连接池的流程:
使用数据库连接池的步骤:
第一次访问的时候,需要建立连接。 但是之后的访问,均会复用之前创建的连接,直接执行SQL语句。
优点:
较少了网络开销
系统的性能会有一个实质的提升
没有TIME_WAIT状态
使用数据库连接池需配置几项参数:
最小连接数:是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费.
最大连接数:是连接池能申请的最大连接数,如果数据库连接请求超过次数,后面的数据库连接请求将被加入到等待队列中,这会影响以后的数据库操作
最大空闲时间
获取连接超时时间
超时重试连接次数
常见的数据库连接池:
BDCP
C3P0
Druid
HikariCP
这里介绍下Druid的优点:
强大的监控特性,通过Druid提供的监控功能,可以清楚知道连接池和SQL的工作情况。
a. 监控SQL的执行时间、ResultSet持有时间、返回行数、更新行数、错误次数、错误堆栈信息;
b. SQL执行的耗时区间分布。什么是耗时区间分布呢?比如说,某个SQL执行了1000次,其中0-1毫秒区间50次,1-10毫秒800次,10-100毫秒100次,100-1000毫秒30次,1-10秒15次,10秒以上5次。通过耗时区间分布,能够非常清楚知道SQL的执行耗时情况;然后就可以对SQL语句进行优化
c. 监控连接池的物理连接创建和销毁次数、逻辑连接的申请和关闭次数、非空等待次数、PSCache命中率等。
方便扩展。Druid提供了Filter-Chain模式的扩展API,可以自己编写Filter拦截JDBC中的任何方法,可以在上面做任何事情,比如说性能监控、SQL审计、用户名密码加密、日志等等。
Druid集合了开源和商业数据库连接池的优秀特性,并结合阿里巴巴大规模苛刻生产环境的使用经验进行优化。
Druid的jar包下载连接:https://mvnrepository.com/artifact/com.alibaba/druid/1.0.20
以后对数据库的连接直接可以通过连接池对数据库连接进行管理,特别方便
举例代码如下
package com.zhouym.mvcpro.vo;
import java.sql.Connection;
import java.sql.SQLException;
import com.alibaba.druid.pool.DruidDataSource;
public class ConnPoolUtil {
//创建数据库连接对象
private static String URL = "jdbc:mysql://localhost:3306/demo?useSSL=true&useUnicode=true&characterEncoding=UTF-8";
//创建驱动信息
private static String DRIVER="com.mysql.jdbc.Driver";
//创建数据库名称
private static String USER="root";
//创建数据库用户密码
private static String PASSWORD="123456";
//实例化一个数据源对象
private static DruidDataSource dataSource = new DruidDataSource();
//使用静态代码块为数据源对象初始化值
static {
//配置连接地址
dataSource.setUrl(URL);
//配置数据库用户名
dataSource.setUsername(USER);
//配置数据库用户密码
dataSource.setPassword(PASSWORD);
//记载驱动信息
dataSource.setDriverClassName(DRIVER);
//设置初始化连接大小
dataSource.setInitialSize(10);
//设置最大连接数
dataSource.setMaxActive(20);
//设置连接最大等待时间
dataSource.setMaxWait(3000);
}
/**
* 和数据库建立连接
* @return
*/
public static Connection getConnection() {
try {
return dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
/**
* 关闭连接,调用这个close方法,是将连接回收到连接池中
* @param conn 连接对象
*/
public static void close(Connection conn) {
if (conn!=null) {
try {
//将连接回收到连接池中
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}