JAVASE-医疗管理系统项目总结

项目功能架构

传统的MVC架构,JavaFX桌面端项目,前端用xml来编写,用工具拖拉拽生成前端。

分为医生模块、药品模块和病人模块,每一个模块都有controller、service、dao、pojo层。

框架采用JDBC,手写了映射、控制反转和依赖注入。

运行截图

image-20240718185946673

​ 根据医生的role来显示不同的页面

​ 登录

image-20240718190000554

​ 主页

实现了组合查询 和分页查询

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可以添加和修改医生

image-20240718190813312

image-20240718190826041

image-20240718190046518

挂号

image-20240718190058557

image-20240718190137643

缴费表 花费+挂号费用25,后面还可以根据不同医生的不同级别还指定挂号费用。

image-20240718190243621

添加药品种类和供应商

image-20240718190339365

采购药品

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

数据统计

image-20240718190430600

image-20240718190446460

系统设置 可以修改密码和切换用户

实现聊天功能(带完善)

image-20240718190521876

数据库设计

一共十一张表结构。

image-20240718172006128

设计模式应用

单列设计模式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文件

image-20240718095740813

//实例化一个对象 
//这样就实现了接口与实现类之间的解耦,通过配置文件灵活地指定实现类。
public JobDao jobDao = (JobDao) BeanFactory.getInstance("JobDao");

事务控制+代理模式

事务的四大特性是ACID,原子性、一致性、隔离性。

多表之间的修改就涉及到事务,我们要确保两次修改要么同时执行成功,要么同时失败。所以在service中需要开启事务。

JDBC setAutoCommit() 关闭启动提交 commit() 提交事务 rollback() 回滚事务

image-20240718094230368

每一个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;
    }
}

image-20240718090351990

image-20240718090358725

这两个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,点击切换对应的页面。

image-20240718091806293

统计模块

统计这一块学习了JDK的Stream流和lamda表达式来进行数据统计。

image-20240718184424863

 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网络编程,实现统一局域网内的同学,建立一个服务端,多个医生是客户端,服务端存储和转发消息。

  • 19
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

渴求sspOffer的小周同学

您的支持是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值