文章目录
- 项目功能架构
- 运行截图
- 数据库设计
- 设计模式应用
- 单列设计模式JDBC模板
- 模板设计模式
- 策略模式
- 工厂设计模式
- 事务控制+代理模式
- 注解开发优化工厂模式
- 页面跳转
- ThreadLocal
- 分页查询实现
- 统计模块
- 聊天
项目功能架构
传统的MVC架构,JavaFX桌面端项目,前端用xml来编写,用工具拖拉拽生成前端。
分为医生模块、药品模块和病人模块,每一个模块都有controller、service、dao、pojo层。
框架采用JDBC,手写了映射、控制反转和依赖注入。
运行截图
根据医生的role来显示不同的页面
登录
主页
实现了组合查询 和分页查询
可以添加和修改医生
挂号
缴费表 花费+挂号费用25,后面还可以根据不同医生的不同级别还指定挂号费用。
添加药品种类和供应商
采购药品
数据统计
系统设置 可以修改密码和切换用户
实现聊天功能(带完善)
数据库设计
一共十一张表结构。
设计模式应用
单列设计模式JDBC模板
Singleton 单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
场景:单例模式只应在有真正的“单一实例”的需求时才可使用。
单例获得数据库连接关闭数据库连接工具类
采用的是 饿汉式单列模式,在类加载时创建实例,不管后期是否使用都会创建
private static JDBCUtil instance = new JDBCUtil();
//初始的一个数据库连接,和数据库值建立一个连接,保证了后面每次不用再关闭流
模板设计模式
简化JDBC开发,原来每个方法都要编写好多的
MyJdbcTemplate类中的方法(如queryObject、insert、queryAll和executeUpd)实现了一种通用的操作步骤,而具体的操作(如如何将结果集映射到对象)由子类或调用方实现。通过这种方式,代码复用了公共的数据库操作步骤,同时允许调用方定制具体的行为。
package com.NJITZX.common.dao;
import com.NJITZX.common.anno.Component;
import com.NJITZX.common.exceptions.DataException;
import com.NJITZX.common.util.JDBCUtil;
import org.apache.commons.lang3.StringUtils;
import java.sql.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@Component
public final class MyJdbcTemplate {
//查询一条记录
public <T> T queryObject(final String SQL, RowMapper<T> rowMapper, Object... parms) throws DataException {
Connection con = JDBCUtil.getCon(); //获得连接
try {
PreparedStatement pst = con.prepareStatement(SQL);
//设置参数
setParmas(pst, parms); //设置参数
ResultSet rst = pst.executeQuery();
if (rst.next()) {
T entity = rowMapper.mapper(rst); //mapper是将rst中值 映射到对象上面来 并返回来;
// System.out.println(entity);
return entity;
}
} catch (SQLException e) {
e.printStackTrace();
throw new DataException("查询记录失败");
}
return null;
}
//添加用户 返回主键
//可变参数
public int insert(final String SQL, Object... parms) throws DataException {
Connection con = JDBCUtil.getCon();
try {
PreparedStatement pst = con.prepareStatement(SQL, Statement.RETURN_GENERATED_KEYS); //设置返回的主键值
setParmas(pst, parms);
int row = pst.executeUpdate();
if (row < 1) {
throw new DataException("新增记录,返回主键值出错");
}
ResultSet rst = pst.getGeneratedKeys(); //得到生成keys
if (rst.next()) {
return rst.getInt(1); //返回第一列
}
} catch (SQLException e) {
e.printStackTrace();
throw new DataException(e.getMessage());
}
return 0;
}
//查询多条语句
public <T> List<T> queryAll(final String SQL, RowMapper<T> rowMapper, Object... parms) throws DataException {
List<T> list = new ArrayList<>();
Connection con = JDBCUtil.getCon();
try {
PreparedStatement pst = con.prepareStatement(SQL);
//设置参数
setParmas(pst, parms);
ResultSet rst = pst.executeQuery();
while (rst.next()) {
T entity = rowMapper.mapper(rst);
list.add(entity);
}
} catch (SQLException e) {
e.printStackTrace();
throw new DataException(e.getMessage());
}
return list;
}
//增删改
public boolean executeUpd(final String SQL, Object... parms) throws DataException {
Connection con = JDBCUtil.getCon(); //得到当前的连接
try {
System.out.println(SQL);
PreparedStatement pst = con.prepareStatement(SQL);
setParmas(pst, parms);
return pst.executeUpdate() > 0;
} catch (SQLException e) {
e.printStackTrace();
throw new DataException(e.getMessage());
}
}
private void setParmas(PreparedStatement pre, Object[] parms) throws SQLException {
if (parms != null && parms.length > 0) {
for (int i = 0; i < parms.length; i++) {
pre.setObject(i + 1, parms[i]);
}
}
}
}
策略模式
RowMapper接口及其实现类DrugMapper体现了策略模式。RowMapper接口定义了一个映射结果集的方法,而DrugMapper提供了具体的映射实现。在执行数据库查询时,可以通过传入不同的RowMapper实现类来改变结果集的映射方式。
ORM(Object-Relational Mapping,面向对象关系映射)是一种通过使用面向对象编程语言来处理数据库的技术。ORM提供了一种从面向对象编程语言到关系型数据库之间的转换机制,使得开发者可以使用对象来直接操作数据库中的数据,而不需要编写复杂的SQL查询。
ORM使得开发者可以使用面向对象的方式操作数据库,无需编写大量的SQL语句,简化了代码编写。
//定义了接口
public interface RowMapper <T>{
T mapper(ResultSet res) throws DataException;
}
//实现了 数据库里面的字段 到对象之间的映射关系
public class DrugMapper implements RowMapper<Drug> {
@Override
public Drug mapper(ResultSet res) throws DataException {
Drug drug=new Drug();
try {
drug.setDrugId(res.getInt(1));
drug.setDrugName(res.getString(2));
drug.setDrugType(res.getInt(3));
drug.setSupport(res.getInt(4));
drug.setProdectDate(res.getString(5));
drug.setEffectDate(res.getInt(6));
drug.setAmount(res.getInt(7));
drug.setPrice(res.getDouble(8));
drug.setDescp(res.getString(9));
drug.setSellPrice(res.getDouble(10));
} catch (SQLException e) {
e.printStackTrace();
}
return drug;
}
}
消息弹出框采用的也是策略模式,写的时候采用匿名内部类的形式
public interface SystemConfirm {
void execute();
}
public class SystemInfo {
//提供系统的确认框
public static void getConfirm(String title, String msg, SystemConfirm info) {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle(title);
alert.setContentText(msg);
Optional<ButtonType> buttonType = alert.showAndWait();
if (buttonType.get() == ButtonType.OK)
info.execute();
}
}
调用
public void deleteDoctor() throws ServiceException {
//匿名内部内
SystemInfo.getConfirm("删除医生", "确定删除医生吗?", new SystemConfirm() {
@Override
public void execute() {
int curID = getSelectID();
if (curID != 0) {
//不为0 开始进行操作;
try {
boolean b = doctorService.deleteById(curID);
if (b == true) {
AlertUtil.alertInfo("恭喜删除成功");
loadPage();//重新加载所有的医生;
}
} catch (ServiceException e) {
e.printStackTrace();
}
}
}
});
在这个代码中,SystemConfirm接口和它的匿名实现类(在deleteDoctor方法中)体现了策略模式。SystemConfirm接口定义了execute方法,而具体的删除医生操作则是在匿名实现类中实现的。
工厂设计模式
Factory 定义一个工厂类,对实现同一个接口的一组类进行实例化对象操作
采用了IOC控制反转的思想,主动new一个对象,变为把对象交给工厂去创建,我们直接从工厂中去拿这个对象即可。 这里还用到了反射的思想
public abstract class BeanFactory {
private static Map<String, Object> beanMap = new HashMap<>(); //把对象放入到hashmao中去;
/*
实列化项目中所有的对象
*/
static {
//读取bean.properties里面的所有属性到is流里面。 将所有的都初始化到map中去
try {
InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
BufferedReader br = new BufferedReader(new InputStreamReader(is,"UTF-8")); //字节流转换成字符流
String data = null;
while ((data = br.readLine()) != null) {
String[] spilt = data.split("="); //对data进行分割
String key=spilt[0]; //key是类名
String cls=spilt[1]; //cls 引用值
Class<?> c=Class.forName(cls);
Object obj= null;
try {
try {
obj = c.newInstance(); //通过反射得到对象
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} catch (InstantiationException e) {
e.printStackTrace();
}
beanMap.put(key,obj);
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//得到一个实列 getInstance();
public static Object getInstance(String key){ //得到对应的key
if (key.contains("Service")){
//进行事务的处理
return TxProxy.getProxy(beanMap.get(key));
}
return beanMap.get(key);
}
}
bean.properties文件
//实例化一个对象
//这样就实现了接口与实现类之间的解耦,通过配置文件灵活地指定实现类。
public JobDao jobDao = (JobDao) BeanFactory.getInstance("JobDao");
事务控制+代理模式
事务的四大特性是ACID,原子性、一致性、隔离性。
多表之间的修改就涉及到事务,我们要确保两次修改要么同时执行成功,要么同时失败。所以在service中需要开启事务。
JDBC setAutoCommit() 关闭启动提交 commit() 提交事务 rollback() 回滚事务
每一个Service都需要进行事务添加,这样明显是很复杂的,所以我们使用代理模式给每一个Service都添加开启事务。
代理模式可以认为是中间商的意思,通过这个中间商给你的service加了一点东西进去。
public interface TXManager {
public void commitTX() throws DataException;
// public void
public void rooBackTx() throws DataException;
public void beginTX() throws DataException;
}
import com.NJITZX.common.exceptions.DataException;
import com.NJITZX.common.tx.TXManager;
import com.NJITZX.common.util.JDBCUtil;
import java.sql.SQLException;
public class TXManagerImpl implements TXManager {
@Override
public void commitTX() throws DataException {
try {
JDBCUtil.getCon().commit();
} catch (SQLException e) {
e.printStackTrace();
throw new DataException("提交事务失败");
}
}
@Override
public void rooBackTx() throws DataException {
try {
JDBCUtil.getCon().rollback();
} catch (SQLException e) {
e.printStackTrace();
throw new DataException("回顾事务失败");
}
}
@Override
public void beginTX() throws DataException {
try {
JDBCUtil.getCon().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
throw new DataException("开启事务失败");
}
}
}
采用jdk的动态代理
所谓动态是指在程序运行前不存在代理类的字节码文件,代理类和委托类的关系是在程序运行时确
定
jdk技术:只适用于实现了接口的类,使用 java.lang.reflect.Proxy
import com.NJITZX.common.exceptions.DataException;
import com.NJITZX.common.tx.TXManager;
import com.NJITZX.common.tx.impl.TXManagerImpl;
import com.NJITZX.common.util.JDBCUtil;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.SQLException;
public class TxProxy {
private static TXManager txManager = new TXManagerImpl();
//getProxy 方法是一个静态泛型方法,用于生成目标对象的代理。targetOb 是要代理的目标对象。
public static <T> T getProxy(final Object targetOb) {
//JVM类加载器
ClassLoader loader = targetOb.getClass().getClassLoader();
// 目标对象实现的接口数组
Class<?>[] interfaces = targetOb.getClass().getInterfaces();
//增强处理类
/**
* 代理对象
* 被代理目标方法
* 方法泪飙
创建一个 InvocationHandler,它定义了代理对象的行为。
invoke 方法在代理对象调用任何方法时被调用。
*/
InvocationHandler h = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws DataException {
try {
txManager.beginTX(); //开启事务
Object returnval = method.invoke(targetOb, args);
txManager.commitTX(); //提交事务
return returnval;
} catch (Exception e) {
e.printStackTrace();
try {
txManager.rooBackTx(); //出现异常回滚事务
throw new DataException("代理失败");
} catch (DataException ex) {
ex.printStackTrace();
throw new DataException("代理失败");
}
}
}
};
return (T) Proxy.newProxyInstance(loader, interfaces, h);
/**
使用 Proxy.newProxyInstance 方法创建代理对象。该方法需要类加载器、接口数组和处理器(InvocationHandler)。
返回类型转换后的代理对象。/
}
}
注解开发优化工厂模式
最初的是new一个,后面是工厂模式,再进阶到抽象工厂。
抽象工厂模式还是有点不好,需要自己去写bean.properties文件,如果bean文件出错并且每次都需要再次添加。
IOC控制反转,将对象交给容器去创建。
//依赖注入
@Retention(RetentionPolicy.RUNTIME) //程序运行的时候执行
@Target(ElementType.FIELD) //作用在属性上面
public @interface Autowired {
}
重写了Beanfactory
import com.NJITZX.common.anno.*;
import com.NJITZX.common.proxy.TxProxy;
import org.reflections.Reflections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
public abstract class BeanFactory {
private static Map<Class, Object> beanMap = new HashMap<>(); //把对象放入到hashmao中去;
//IOC容器
static {
//扫描包
Reflections reflections = new Reflections("com.NJITZX");
Set<Class<?>> comSet = reflections.getTypesAnnotatedWith(Component.class);
Set<Class<?>> respSet = reflections.getTypesAnnotatedWith(Respository.class);
Set<Class<?>> serviceSet = reflections.getTypesAnnotatedWith(Service.class);
//将扫描的信息存放集合
Set<Class<?>> beanClassSet = new LinkedHashSet<>();
beanClassSet.addAll(comSet);
beanClassSet.addAll(respSet);
beanClassSet.addAll(serviceSet);
//反射创建对象
beanClassSet.forEach(beanClass -> {
try {
//通过反射创建对象
Object obj = beanClass.newInstance();
//获取类实现的接口
Class<?>[] interfaces = beanClass.getInterfaces();
//如果没有接口
Class key = beanClass;
if (interfaces != null && interfaces.length > 0) {
key = interfaces[0];
}
beanMap.put(key, obj); //类名或者接口名作为键值
System.out.println(key+","+beanMap.get(key));
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
});
//实现依赖注入DI
beanMap.values().stream().forEach(beanInstance -> {
Stream.of(beanInstance.getClass().getDeclaredFields()).filter(field -> field.getAnnotation(Autowired.class) != null)
.forEach(field -> {
field.setAccessible(true); //设置属性可以访问
//获取类型 key
Class<?> type = field.getType();
//根据键值获取对象
Object o = beanMap.get(type);
try {
field.set(beanInstance, o);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
});
});
}
//
public static <T> T getInstance(Class<T> cls) {
Object obj = beanMap.get(cls);
Set<Class> classes = beanMap.keySet();
for(Class c:classes){
System.out.println(c+" 空格 "+beanMap.get(c));
}
Transaction annotation = obj.getClass().getAnnotation(Transaction.class);
if (annotation == null) {
return (T) obj;
}
return TxProxy.getProxy(obj);
}
页面跳转
页面跳转类似于前端的路由,本质上都是一样的。vue项目添加路由,可以实现页面跳转。
手写了一个UImanager来控制和管理页面的跳转。
我们将页面加载分为 **懒加载和理解加载,**懒加载是只有点击页面的时候才会去加载。理解加载是程序一启动就会加载。用枚举类来标识两种加载方式。
public enum LoadType {
//枚举类 立即加载 和懒加载
EAGER,
LAZY;
}
UIconstant也是枚举类,重写了toString方法,返回了页面的URL,给FX去加载。
public enum UIConstant {
//用户页面显示
Doctor(LoadType.EAGER) {
@Override
public String toString() {
return "/com/NJITZX/doctor/view/doctor.fxml";
}
},
Doctor_ADD(LoadType.LAZY) {
@Override
public String toString() {
return "/com/NJITZX/doctor/view/addDoctor.fxml";
}
},
Doctor_PASS(LoadType.EAGER) {
@Override
public String toString() {
return "/com/NJITZX/doctor/view/pass.fxml";
}
},
Patient_LIST(LoadType.LAZY) {
@Override
public String toString() {
return "/com/NJITZX/patient/view/patinet.fxml";
}
},
Doctor_UPDATE(LoadType.LAZY) {
@Override
public String toString() {
return "/com/NJITZX/doctor/view/updateDoctor.fxml";
}
}, DrugType_ADD(LoadType.LAZY) {
@Override
public String toString() {
return "/com/NJITZX/drug/view/drugtype.fxml";
}
}, Echarts_Doctor(LoadType.LAZY) {
@Override
public String toString() {
return "/com/NJITZX/doctor/view/showdata.fxml";
}
}, Pass_Change(LoadType.LAZY) {
@Override
public String toString() {
return "/com/NJITZX/doctor/view/chagePass.fxml";
}
}, Drug_ADD(LoadType.LAZY) {
@Override
public String toString() {
return "/com/NJITZX/drug/view/AddDrug.fxml";
}
}, Patient_Register(LoadType.LAZY) {
@Override
public String toString() {
return "/com/NJITZX/patient/view/register.fxml";
}
}, Chat_Init(LoadType.LAZY) {
@Override
public String toString() {
return "/com/NJITZX/chat/view/chat.fxml";
}
}, Patine_Diagnose(LoadType.LAZY) {
@Override
public String toString() {
return "/com/NJITZX/patient/view/diagnose.fxml";
}
}, Work(LoadType.LAZY) {
@Override
public String toString() {
return "/com/NJITZX/patient/view/work.fxml";
}
}, Pay(LoadType.LAZY) {
@Override
public String toString() {
return "/com/NJITZX/patient/view/pay.fxml";
}
}, Drug(LoadType.LAZY) {
@Override
public String toString() {
return "/com/NJITZX/drug/view/drug.fxml";
}
};
private LoadType loadType;
UIConstant(LoadType loadType) {
this.loadType = loadType;
}
public LoadType getLoadType() {
return loadType;
}
}
public class UIManger {
private static Map<UIConstant,Object> nodeMap = new HashMap<>();
//初始化,存入到map集合中
public static void initView(){
UIConstant[] valueAy = UIConstant.values();
System.out.println(Arrays.toString(valueAy));
for(UIConstant uiConstant:valueAy){
String viewUrl = uiConstant.toString(); //得到那一串URL
System.out.println(viewUrl);
//立即加载
try {
if(uiConstant.getLoadType()==LoadType.EAGER) {
//理解加载 fxmlloader 理解加载
Parent panel = FXMLLoader.load(UIManger.class.getResource(viewUrl));
nodeMap.put(uiConstant, panel);
}else if(uiConstant.getLoadType()==LoadType.LAZY){
//懒加载 存放到集合中去
nodeMap.put(uiConstant, viewUrl);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//跳转页面 需要传入一个观察者列表和URL
public static void show(ObservableList<Node> observableList,UIConstant uiConstant){
observableList.clear(); //清除当前的页面
if(!nodeMap.containsKey(uiConstant)){
throw new RuntimeException("请先注册当前界面,没有相应fxml文件");
}
Node node = null;
if(uiConstant.getLoadType()==LoadType.EAGER){
node=(Node)nodeMap.get(uiConstant); //理解记载直接得到对象即可
}else if(uiConstant.getLoadType()==LoadType.LAZY){
String viewUrl = (String)nodeMap.get(uiConstant);
try {
//通过反射,加载到内存
node = FXMLLoader.load(UIManger.class.getResource(viewUrl));
} catch (IOException e) {
e.printStackTrace();
}
}
observableList.add(node); //将node添加到观察者列表去
}
MainController 里面创建了观察者列表。 初始观察者列表是anchorpane的child组件。
public static ObservableList<Node> observableList;
this.observableList = anchorPane.getChildren();
ThreadLocal
比如我要修改医生信息,点击修改之后如何回显医生信息呢,因为我们每一个页面都是独立的,不是在一个类里面的,所以我们采用ThreadLocal线程来存放id,然后根据id找到这个医生,并回显数据。
public class BaseContext {
private static ThreadLocal<Integer> DoctorLocal = new ThreadLocal<>();
public static void setId(Integer id) {
DoctorLocal.set(id);
}
public static Integer getCurId() {
return DoctorLocal.get();
}
}
ThreadLocal 实际开发使用的时候会有内存泄漏的问题,这个面试会经常问道。因为Map里面的那个key是强引用的,使用后不能自动回收,会让内存移除,我们需要手动的将loca移除内存。
分页查询实现
没有用工具类,手写了一个分页。
从Util-jdbc-dao-service-controller分析。
PageUtil 包含四个成员变量
currentPage 当前所在页 通过按钮不断切换
pageNum 总页数
pagesize 每一页的大小
totalcount 总记录数
package com.NJITZX.common.util;
public class PageUtil {
//当前页
private int currentPage;
//总页数
private int pageNum;
//每一页默认的大小
private int pagesize = 4;
//总共行数
private int totalCount;
public PageUtil() {
}
public PageUtil(int currentPage, int pageNum, int pagesize, int totalCount) {
this.currentPage = currentPage;
this.pageNum = pageNum;
this.pagesize = pagesize;
this.totalCount = totalCount;
}
public int getCurrentPage() {
return currentPage;
}
//设置当前页数
public void setCurrentPage(int currentPage) {
this.currentPage = currentPage;
if (currentPage < 1) {
this.currentPage = 1;
}
if (currentPage >= this.pageNum) {
this.currentPage = this.pageNum;
}
}
public int getPageNum() {
return pageNum;
}
//需要判断
public void setPageNum() {
//总页数需要 根据查询所有记录和一页的大小进行判断
this.pageNum = totalCount % pagesize == 0 ? (totalCount / pagesize) : (totalCount / pagesize + 1);
}
public int getPagesize() {
return pagesize;
}
public void setPagesize(int pagesize) {
this.pagesize = pagesize;
}
public int getTotalCount() {
return totalCount;
}
public void setTotalCount(int totalCount) {
this.totalCount = totalCount;
}
}
这两个set方法自己重写,当前页不能小于1也不能大于最大页数。
总页数,如果总行数/每一页==0,直接取值,不然需要+1。
前端需要传入分页查询的条件等,形式是键-值的形式,所以我们使用Map来存放。
default Long count(Map<String, String> map) throws DaoException {
return null;
}
//根据条件查询每一页的记录数
default List<T> selectByCond(Map<String, String> map) throws DaoException {
return null;
}
在BaseDao接口里面加入default方法。 一个是返回总行数,一个是返回每一页的记录。
在模板模式jdbc里加入这两个方法;
条件查询本质就是 where 进行条件拼接。传入的sql 前面加上where 1=1 这个条件是用真的。拼接的时候and like
—即可;
Map里面的参数包括,传入的查询条件,比如logname-张三 sex-女
还有 offset 当前页 和size 每一页的大小。
public StringBuilder dySQL(final String baseSQL, Map<String, String> mp) {
StringBuilder sb = new StringBuilder(baseSQL);
if (mp != null && !mp.isEmpty()) {
List<String> skips = Arrays.asList("offset", "size");
mp.forEach((k, v) -> {
//todo
//where id like CONCAT('%','1','%')
//拼接查询语句 跳过offset和size
if (!skips.contains(k) &&StringUtils.isNotEmpty(v)) { //不是上面那个
sb.append(" and ").append(k).append(" like CONCAT('%', '").append(mp.get(k)).append("','%')");
}
});
// ystem.out.println(sb.toString());S
}
return sb;
}
//得到所有的count //条件
public long getCount(final String SQL, Map<String, String> map) throws DataException {
//前面sql 是固定的 需要进行拼接 奇数的时候 拼接
Connection connection = JDBCUtil.getCon();
StringBuilder sb = dySQL(SQL, map);
System.out.println(sb.toString());
PreparedStatement pre = null;
try {
pre = connection.prepareStatement(sb.toString());
ResultSet resultSet = pre.executeQuery();
if (resultSet.next()) {
return resultSet.getInt(1);//只有第一列 总行数
}
} catch (SQLException e) {
e.printStackTrace();
throw new DataException(e.getMessage());
}
return 0L;
}
public <T> List<T> getListPage(final String SQL, Map<String, String> map, RowMapper<T> rowMapper) throws DataException {
List<T> list = new ArrayList<>();
Connection connection = JDBCUtil.getCon();
StringBuilder sb = dySQL(SQL, map);
sb.append(" limit "); //limit 关键字
sb.append(Integer.parseInt(map.get("offset"))); // offset 总当前多少行开始 需要根据当前页数和每一页的大小来计算
sb.append(" , ");
sb.append(Integer.parseInt(map.get("size"))); //查询的大小
System.out.println(sb.toString());
PreparedStatement pre = null;
try {
pre = connection.prepareStatement(sb.toString());
ResultSet resultSet = pre.executeQuery();
while (resultSet.next()) {
T entity = rowMapper.mapper(resultSet);
list.add(entity); //将实体添加进来
}
} catch (SQLException e) {
e.printStackTrace();
throw new DataException(e.getMessage());
}
return list; //返回接口
}
在Dao里面进行查询
//新增加的接口
@Override
public List<DoctorVo> listByPage(Map<String, String> map) throws DaoException {
String sql = "SELECT\n" +
" f.doctorid,\n" +
" f.name,\n" +
" f.gender,\n" +
" f.birthday,\n" +
" j.jobname,\n" +
" e.ename,\n" +
" d.departname,\n" +
" l.role,\n" +
" l.loginname,\n" +
" l.password\n" +
"FROM t_doctor f\n" +
"INNER JOIN t_login l ON f.doctorid = l.uid\n" +
"INNER JOIN t_education e ON f.eid = e.eid\n" +
"INNER JOIN t_jobtitle j ON f.jid = j.jobid\n" +
"INNER JOIN t_department d ON f.departid = d.departid where 1=1 "; //主要where1=1;
try {
return jdbcTemplate.getListPage(sql, map, new DoctorVoMapper());
} catch (DataException e) {
e.printStackTrace();
throw new DaoException(e.getMessage());
}
}
@Override
public Long count(Map<String, String> map) throws DaoException {
String sql = "SELECT count(1) \n" +
"FROM t_doctor f\n" +
"INNER JOIN t_login l ON f.doctorid = l.uid\n" +
"INNER JOIN t_education e ON f.eid = e.eid\n" +
"INNER JOIN t_jobtitle j ON f.jid = j.jobid\n" +
"INNER JOIN t_department d ON f.departid = d.departid where 1=1";
try {
return jdbcTemplate.getCount(sql, map);
} catch (DataException e) {
e.printStackTrace();
throw new DaoException(e.getMessage());
}
}
Serivce层
// offset -->page 页数
// size 每一页的显示数目
@Override
public List<DoctorVo> listByPage(Map<String, String> map) throws ServiceException {
long page = 0L; //当前页
long size = 0L; //每一页记录数
if (map.containsKey("page")) { //当前页
page = Long.parseLong(map.get("page"));
map.remove("page");
}
if (map.containsKey("size")) {
size = Long.parseLong(map.get("size"));
}
long offset = (page - 1) * size; //从(page-1)*size开始 就是sql语句忽略的行数开始
map.put("offset", String.valueOf(offset));
//获取每一页记录数
try {
List<DoctorVo> doctorVos = doctorInfoDao.listByPage(map);
return doctorVos;
} catch (DaoException e) {
e.printStackTrace();
throw new ServiceException(e.getMessage());
}
}
@Override
public long countInfo(Map<String, String> map) throws ServiceException {
Long count = null;
try {
count = doctorInfoDao.count(map);
} catch (DaoException e) {
e.printStackTrace();
throw new ServiceException(e.getMessage());
}
return count;
}
Controller层
首页 上一页 下一页 尾页 都需要绑定一个action,点击切换对应的页面。
统计模块
统计这一块学习了JDK的Stream流和lamda表达式来进行数据统计。
public PieChart echarts; //创建的饼状图
public DrugTypeService drugTypeService =BeanFactory.getInstance(DrugTypeService.class);
public void loadSexEcharts() {
ObservableList<PieChart.Data> pieChartData = FXCollections.observableArrayList();
try {
Stream<Drug> drugs = drugService.listAll().stream();
//根据性别进行分组 stream流
Map<Integer, List<Drug>> fenzu =
drugs.collect(Collectors.groupingBy(Drug::getDrugType));
Set<Integer> integers = fenzu.keySet();
//这一块还可以进行优化
for (Integer c : integers) {
//按照类别分组后
List<Drug> drug = fenzu.get(c);
int amount = 0;
for (Drug d : drug) {
amount += d.getAmount();
}
//根据id得到类别
DrugType drugType = drugTypeService.getDrugType(c);
//向观察者列表添加数据
pieChartData.add(new PieChart.Data(drugType.getCname(), amount));
}
} catch (ServiceException e) {
e.printStackTrace();
}
echarts.setData(pieChartData);
echarts.setClockwise(true);
echarts.setPrefHeight(400);
echarts.setPrefWidth(400);
echarts.setTitle("各种类别药品比列");
}
聊天
这一块还没有做完整。
想要展示当前在线医生,可以在login上面添加一个字段,当医生登录或者退出的的时候会改变这个字段。
聊天目前采用的方式想是通过time定时器来实现定时轮询,但是线程这一块报异常,应该是前面数据库连接采用了单列模式,出现了一些问题。
同时给出其他的方案。
1.采用websocket+netty进行消息推送,当后端接口了消息的时候,根据消息的接收方推送消息到前端来,这个会在后面用boot框架去做
2.采用socket网络编程,实现统一局域网内的同学,建立一个服务端,多个医生是客户端,服务端存储和转发消息。