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

news2024/9/22 3:53:34

文章目录

    • 项目功能架构
    • 运行截图
    • 数据库设计
    • 设计模式应用
      • 单列设计模式JDBC模板
      • 模板设计模式
      • 策略模式
      • 工厂设计模式
      • 事务控制+代理模式
      • 注解开发优化工厂模式
    • 页面跳转
    • ThreadLocal
    • 分页查询实现
    • 统计模块
    • 聊天

项目功能架构

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1935557.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

水经微图Web版1.10.0发布

水经微图&#xff08;简称“微图”&#xff09;新版已上线&#xff0c;在该版本中主要新增了三调图例符号库&#xff0c;以及其它功能的优化。 当前版本 当前版本号为&#xff1a;1.10.0-beta.2 如果你发现该版本中存在问题&#xff0c;请及时反馈给我们修订。 关于我们产品…

Adobe国际认证详解-影视后期

在当今的数字媒体时代&#xff0c;影视后期制作作为创意产业的核心环节&#xff0c;对于专业技能的要求日益提高。Adobe国际认证&#xff0c;作为全球创意设计领域的重要标杆&#xff0c;为影视后期制作人员提供了一个展示自我、提升技能的国际舞台。 何为影视后期&#xff1f;…

javaEE (3)

Json json--JavaScript object notation (js对象表现形式) 在后端将java对象转为json格式的字符串 有很多第三方组件,可以直接将java对象转为json格式的字符串 new objectMapper().writeValueAsString(); 返回string类型 <!-- jackson--><dependency>&…

华为od机试真题 — 分披萨(Python)

题目描述 “吃货”和“馋嘴”两人到披萨店点了一份铁盘(圆形)披萨&#xff0c;并嘱咐店员将披萨按放射状切成大小相同的偶数个小块。 但是粗心服务员将披萨切成了每块大小都完全不同奇数块&#xff0c;且肉眼能分辨出大小。 由于两人都想吃到最多的披萨&#xff0c;他们商量…

Open3d入门 点云拼接算法

点云拼接&#xff08;Point Cloud Stitching&#xff09;是将从不同视角或位置获取的多组点云数据对齐到同一个坐标系中的过程&#xff0c;以形成一个完整的三维模型。这项技术在计算机视觉、机器人导航、三维重建和无人驾驶等领域有着广泛的应用。 点云配准&#xff08;Point…

Qt 制作安装包

记录使用Qt工具制作一个安装包的过程 目录 1.准备工作 1.1检查Qt Installer Frameworks是否安装 1.2.安装Qt Installer Frameworks 1.3准备release出来的exe dll等文件 2.创建打包工程所需要的文件及目录 2.1创建子目录 2.2 创建工程文件 2.3 创建config/config.xml …

5.4 软件工程-系统设计

系统设计 - 概述 设计软件系统总体结构 数据结构及数据库设计 编写概要设计文档、评审 详细设计的基本任务 真题

HDU1011——Starship Troopers(树形DP),HDU1012——u Calculate e,HDU1013——Digital Roots

目录 HDU1011——Starship Troopers&#xff08;树形DP&#xff09; 题目描述 运行代码 代码思路 树形DP HDU1012——u Calculate e 题目描述 运行代码 代码思路 HDU1013——Digital Roots 题目描述 超时代码 改进后依旧超时代码 运行代码 代码思路 HDU1011——…

工时记录软件选型指南

国内外主流的10款工时计算软件对比&#xff1a;PingCode、Worktile、Tita、易企秀、奇鱼、Teambition、Timely、Toggl Track、RescueTime、ClickUp。 在忙碌的工作中&#xff0c;记录和管理工时常常是令人头疼的问题。工时记录软件的选择不仅能帮你省时省力&#xff0c;还能大幅…

视频素材网站无水印的有哪些?热门视频素材网站分享

当我们走进视频创作的精彩世界时&#xff0c;一个难题常常摆在面前——那些高品质、无水印的视频素材究竟应该在哪里寻找&#xff1f;许多视频创作者感叹&#xff0c;寻找理想的视频素材难度甚至超过了寻找伴侣&#xff01;但不用担心&#xff0c;今天我将为您介绍几个优质的视…

理解UI设计:UI设计师的未来发展机遇

UI设计师的出现是互联网时代的设计变革。随着移动互联网的快速发展&#xff0c;移动产品设计师非常短缺。高薪资让许多其他行业的设计师已经转向了UI设计。那么什么是UI设计呢&#xff1f;UI设计师负责什么&#xff1f;UI设计的发展趋势和就业前景如何&#xff1f;这些都是许多…

C++仓库管理系统

功能 代码在效果图后面 1.添加物品 2.删除物品 3.更新物品数量 4.查询物品 5.列出所有物品 6.保存并退出 注意事项&#xff1a;退出要输入“6”退出才能保存数据&#xff0c;不要直接按X关掉窗口&#xff08;不会保存数据&#xff09;。 效果图 源代码 编…

C语言 底层逻辑详细阐述指针(一)万字讲解 #指针是什么? #指针和指针类型 #指针的解引用 #野指针 #指针的运算 #指针和数组 #二级指针 #指针数组

文章目录 前言 序1&#xff1a;什么是内存&#xff1f; 序2&#xff1a;地址是怎么产生的&#xff1f; 一、指针是什么 1、指针变量的创建及其意义&#xff1a; 2、指针变量的大小 二、指针的解引用 三、指针类型存在的意义 四、野指针 1、什么是野指针 2、野指针的成因 a、指…

Unity客户端接入原生Google支付

Unity客户端接入原生Google支付 1. Google后台配置2. 开始接入Java部分C#部分Lua部分 3. 导出工程打包测试参考踩坑注意 1. Google后台配置 找到内部测试&#xff08;这个测试轨道过审最快&#xff09;&#xff0c;打包上传&#xff0c;这个包不需要接入支付&#xff0c;如果已…

机器人开源调度系统OpenTcs6-架构运行分析

系统启动 启动 Kernel&#xff1a;加载核心应用&#xff0c;初始化系统配置和状态。 启动 Plant Overview&#xff1a;加载图形用户界面&#xff0c;初始化模型和用户界面。 模型导入和配置 在 Plant Overview 中导入或创建工厂布局模型。 配置路径、位置和车辆信息。 车辆连…

用DrissionPage过某里滑块分析

最近我又在找工作了&#xff0c;悲哀啊~&#xff0c;面试官给了一道题&#xff0c;要求如下&#xff1a; 爬虫机试&#xff1a;https://detail.1688.com/offer/643272204627.html 过该链接的滑动验证码&#xff0c;拿到正确的商品信息页html&#xff0c;提取出商品维度的信息&a…

排序一次讲清(从冒泡到基数)

文章目录 冒泡原理代码pythonc 选择原理代码pythonc 插入原理代码pythonc 希尔原理代码pythonc 快排原理代码pythonc 归并原理代码pythonc 堆原理代码pythonc 计数原理代码pythonc 桶原理代码pythonc 基数原理代码pythonc 【待更新】 冒泡 原理 如果我们想要让数组从左至右从…

海豚调度器(DolphinScheduler)集群搭建详细笔记

海豚调度器集群搭建笔记 1.DolphinScheduler Cluster部署1.1 集群部署规划1.2 集群准备工作1.3 初始化数据库1.4 修改安装环境配置1.5 安装DolphinScheduler1.6 启停命令1.7 登录 DolphinScheduler UI 1.DolphinScheduler Cluster部署 分布式去中心化易扩展的工作流任务调度系…

【最强八股文 -- 计算机网络】TCP 四次挥手的过程及原因

第一次挥手&#xff1a;FIN 报文第二次挥手&#xff1a;ACK 报文第三次挥手&#xff1a;FIN 报文第四次挥手&#xff1a;ACK 报文 为什么需要四次挥手&#xff1f; 为什么需要 TIME_WAIT 状态&#xff1f; TIME_WAIT 的时间为什么是 2MSL&#xff1f;

springboot服务如何执行sql脚本文件

当sql脚本文件包含不同数据库实例sql时&#xff0c;遍历读取sql文件再插入时&#xff0c;由于是不同的数据库实例这种方式就不行了&#xff0c;这时就需要程序直接执行sql脚本。 springboot执行sql脚本 /*** 执行sql脚本* throws SQLException*/ private void executeSqlScri…