个人开发经历--我的java学习之路(学校篇)

编程知识 行业动态 更新时间:2024-06-13 00:21:19

个人开发经历--我的java学习之路(学校篇)

  • 个人介绍:
    • 姓名: 不在这里说明
    • 联系信息:
  • 个人历程
    • jdbc阶段
      • sql生成器
      • 一代代码生成器
    • servlet阶段
      • servlet项目中,sql生成器崩溃
      • sql生成器崩溃后的补救 --> MyBatis && Maven
      • 项目结束,整理公共代码,
    • SSM
      • 写完SSM文章后,升级代码生成器
      • 我的SSM与学校的Oracle(即将放寒假)
      • 假期
      • 收假后第一两个星期
    • springBoot 阶段
      • 《个人英语单词》项目(先用SSM做了一点点,改SpringBoot,再暂停)
      • srpingBoot项目(一:(学校宿舍管理系统))
        • MyBatisPlus(V:2.2)
          • 处理分页
            • 循环分页,页码
            • 个性化定制显示数据
          • 意外的收获:ServiceImpl
          • 回头再次解决limit参数问题
        • Maven分模块
      • 继续写《个人英语单词》项目
        • 反向思维,共用页面
    • github & linux

个人介绍:

姓名: 不在这里说明

联系信息:

平台: QQ/微信/小破站/gitHub
gitHub: https://github/kkzxm
统一昵称: 酷酷宅小明
头像:

代码感悟:
越少的代码,意味着越少的Bug,更加意味着越少的维护成本!
论代码行数来判定工资…我不敢去评判,更加不敢去想象这样的场景

提前名词解释:
模块:在本文中,模块并不是指maven中的模块,而是,
一个entity类如:student实体类 + Student持久层类 + Student业务层 + Student控制层类的一个总称,
或许这样称呼,它是不对的,但用在这篇文章,刚好合适

个人历程

(排序方法并非依照 技术包含 来排序,而是在学这技术的时候,顺手做了,或者是顺手学了)

jdbc阶段

在学习jdbc时,老师也对整体的项目结构做了一些优化,具体如何优化一两句话真说不清楚,总之,是用到了jdbc的元数据,以及其它的jdbcUtils等工具。


sql生成器

上面这些都不重要,但在这个过程中,我发现每个sql语句都基本上是固定的,查询前置部分都是
select * from 表名 where …
在当时就让我萌生了想要做一个东西,来简化jdbc的开发(当时,根本就没有框架的概念),我把它叫做:sql生成器
原理:StringBuffer字符串一个字符一个字符地拼接(当时也不知道有xml这个东西)
于是,在接下来的两个月的时间里,我因为需要做这个东西,把当时还没学到的反射给学了:获取类名,来替代
表名*,对于结果集的映射,也完全由反射来完成,
总结: 该方法对单表无敌,只要调个service层的方法,其它的完全自动,不用管,但…
在做上面这个sql语句生成器的时候,发现其实每个模块的代码都是差不多的,于是顺手做了一个代码生成器:


一代代码生成器

原理:StringBuffer字符串拼接

  1. 第一个版本:
    (1) 数据来源:txt文件
    (2) 文件内容:(因为源代码已经丢失,可能原本在之前是这么写的)
1备注  类名1{
	 属性1备注:属性1类型 属性1标识符;
	 属性2备注:属性2类型 属性2标识符;
	 .....等等...
},
类2备注 类名2{
	属性1备注:属性1类型 属性1标识符;
	属性2备注:属性2类型 属性2标识符;
	.....等等...
}

(3)附加说明:

类备注与属性备注都是在sql生成器里用到的,好像是当要查询某个属性的值,
不用去输这个属性的标识符,而是添加在该属生上的注解,如:

public class{
	....
	@Comment("学生姓名")
	private String studentName;
	....
}

@Comment(“学生姓名”)
(通过备注可以找到属性,通过属性也可以找到备注)

比如:

studentService.selectByAttr("学生姓名","酷酷宅小明");
studentService.selectByAttr("studentName","酷酷宅小明");
//上面两句执行后,返回的结果是同一条数据

(4)总结:
txt能容纳的信息太少,太少,如果想要容纳更多的信息,则意味着更大的解析难度,而且,更容易报错(比如:txt文档里找空格)
再后来,学习了dom4j,第一次知道了有xml这个东西,当机立断,舍弃了txt这个解析方法,但也仅仅只是舍弃了txt解析方式,其它的还是没有任何改变(比如,它只能生成实体层代码,仅此而已)

上面sql生成器和代码生成器,源码已经不在了,但思路基本就是这样,

(5)回忆:记得当时做测试时,先创建好表,然后,再通过表结构,手写txt文档,或者xml文档,调用代码生成器入口,生成好实体层代码…


此时,还没到真正做项目的时候,下面才是高潮


servlet阶段

新学期,我们换了老师,
servlet + jsp的学习,我是自学的,
我坐在教室的最后一桌,两个显示器,一边看着小破站的教学视频,一边敲着自己的东西,
大家都知道的,
servlet需要继承于HttpServlet这个类,并重写doGet或doPost其中一个方法,
且一个servlet类只能处理一个请求,
那如何让一个servlet处理多个请求呢?
答案是:不去直接继承于HttpServlet,而是写一个BaseServlet类,用它来继承HttpServelt,且每次请求时添加一个请求:active,接下来就是反射的一顿操作(active与方法名保持一致),成功解决问题!
但我并不会满足于此!

我的BaseServlet(项目中写的,学的时候没有人会这么教)

/**
 * @Author: 酷酷宅小明
 * <p>
 */
public abstract class BaseServlet extends HttpServlet {
	//通过这个属性active,实现一个servelt处理多个请求
    protected String active;
    
    protected HttpServletRequest req;
    protected HttpServletResponse resp;
    
    //自动配置的entity
    protected Object entity;
    
    //图片(文件上传时用到的)
    protected TuPian tuPian = new TuPian();
    //管理员()
    private Manager manager;

	//这个由子类去实现,通过这个方法,可以让父类知道entity是谁,等等信息
    abstract protected void setEntityAndSVC();
    
    //region  doGet与doPost
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        try {
            setInfo(req, resp);
            getActive();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
        try {
            setInfo(req, resp);
            getActive();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //endregion

    /**
     * 每次在doGet或doPost方法执行时,
     * 一定会第一时间来执行这里,
     * 内部方法,设置编码方式以及ObjectSvc等
     */
    private void setInfo(HttpServletRequest req, HttpServletResponse resp) throws Exception {
    	//给上面的req与resp赋值
        this.req = req;
        this.resp = resp;
        //设置字符编码,每次多写一个方法,都要去设置一次字符编码,怕不是 石乐志 转世
        setCharacter();
        //设置实体对象,以及service(给反射装配做准备)
        setEntityAndSVC();
        //设置全局管理员(当时项目搞复杂了,本来用不着这个的)
        setManager();
        //这里是考虑到文件上传时
        if (formIsMultipart()) {
        	//如果是文件上传的话,通过普通方法,无法装配entity对象
            fileInput();
        } else {
        	//如果不是文件上传的话,通过普通方法取到各种需要的值
            active = req.getParameter("active");
            //设置当前操作的实体对象entity
            Map parameterMap = req.getParameterMap();
            /*
            这个这前是通过BeanUtils的jar包来做的,
            但那jar包.参数多一个都不行,直接报错...然后就放弃了,
            自己试着模仿它的原理,做了一个
            */
            setObInfo(entity, parameterMap);
        }
    }

    /**
     * 设置全局管理员
     */
    private void setManager() {
        Object manager = req.getSession().getAttribute("manager");
        if (manager != null) {
            this.manager = (Manager) manager;
        }
    }

    /**
     * 设置字符编码
     */
    private void setCharacter() throws UnsupportedEncodingException {
        req.setCharacterEncoding("utf-8");
        resp.setContentType("text/html;charset=UTF-8");
    }

    /**
     * 查看表单是否是分段提交的
     */
    private boolean formIsMultipart() {
        return ServletFileUpload.isMultipartContent(req);
    }

    /**
     * 得到反射对象的active去调用对应的方法
     */
    private void getActive() {
        try {
            Method activeMethod = this.getClass().getDeclaredMethod(active);
            activeMethod.invoke(this);
            req = null;
            resp = null;
        } catch (Exception e) {
            System.out.println(active);
            e.printStackTrace();
        }
    }

    //region 处理文件上传
	/*
	这里,当时项目用的是绝对路径,所以,在我的电脑上没问题,
	但演示时,用的是别人的电脑,图片上传成功后,,,,图片报了404,
	也因为这个原因,比赛没能拿到名次
	*/
    /**
     * 文件上传的方法
     */
    private void fileInput() throws Exception {
        FileItemFactory fileItemFactory = new DiskFileItemFactory();
        ServletFileUpload servletFileUpload = new ServletFileUpload(fileItemFactory);
        List<FileItem> fileItems = servletFileUpload.parseRequest(req);
        Map keyVal = new HashMap();
        for (FileItem fileItem : fileItems) {
            String fieldName = fileItem.getFieldName();
            String val = "";
            if (fileItem.isFormField()) {
                //普通表单
                val = fileItem.getString("UTF-8");
                if (fieldName.equals("active")) active = val;
            } else {
                //文件上传
                //原文件名
                String fileName = fileItem.getName();
                //得到后缀名
                String fileSuffix = getFileSuffix(new File(fileName));
                //当前时间字符串
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmssSS");
                String format = simpleDateFormat.format(new Date());
                //当前时间与后缀名拼接(得到新文件名)
                String newFileName = format + fileSuffix;
                //将新文件名赋给图片的name属性
                tuPian.setTuPianName(newFileName);
                //给图片加上管理员外键
                tuPian.setManager(manager);
                //将图片地址等信息写入数据库
                new TuPianServiceIMP().addTuPian(tuPian);
                //文件写入写入磁盘
                fileItem.write(new File("/img/" + newFileName));
                //回查最新的一张图片Id
                int lastTuPianId = lastTuPianId().getTuPianId();
                //将最后一张图片的id交给val
                val = String.valueOf(lastTuPianId);
            }
            keyVal.put(fieldName, val);
            setObInfo(entity, keyVal);
        }
    }
    //endregion

    //endregion
    /**
     * 查看数据库中最后一张图片
     */
    private TuPian lastTuPianId() {
        TuPianService tuPianService = new TuPianServiceIMP();
        TuPian lastTuPian = tuPianService.findLastTuPian(tuPian);
        return lastTuPian;
    }

    /**
     * 该方法在用到验证码的情况下调用
     * 注:验证码的输入框,name值为 verification<br/>
     * 相同返回true,不相同返回false;
     *
     * @return 查看输入的验证码值与输入的是否相同,
     */
    protected boolean getYzmFlag() {
        HttpSession session = req.getSession();
        String yzm_key = String.valueOf(req.getSession().getAttribute("KAPTCHA_SESSION_KEY"));
        session.removeAttribute("KAPTCHA_SESSION_KEY");
        String verification = req.getParameter("verification");
        boolean equals = yzm_key.equals(verification);
        if (!equals) req.setAttribute("verificationError", "信息错误!或!重复提交表单");
        else req.removeAttribute("verificationError");
        return equals;
    }

    //region 转发与重定向

    /**
     * 转发与重定向的总方法
     * 在该方法中最后一个值,代表着选择的方向:
     * 可选值:<br/>
     * 转发前端<br/>
     * 重定向前端<br/>
     * 转发后端<br/>
     * 重定向后端<br/>
     *
     * @param pageName 文件名/资源名
     * @param val      选择的方向
     */
    protected void zfOrCdx(String val, String pageName) {
        switch (val) {
            case "转发前端":
                transmitFrontPage(pageName);
                break;
            case "转发后端":
                transmitBackPage(pageName);
                break;
            case "重定向前端":
                redirectFrontPage(pageName);
                break;
            case "重定向后端":
                redirectBackPage(pageName);
                break;
            default:
                System.out.println(val + "输入错误");
                break;
        }
    }

    //region 转发与重定向->底

    /**
     * 转发的方法
     */
    protected void transmit(String pageName) {
        try {
            System.out.println(pageName);
            req.getRequestDispatcher(pageName).forward(req, resp);
        } catch (ServletException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 重定向
     */
    protected void redirect(String pageName) {
        try {
            resp.sendRedirect(pageName);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //endregion

    //region 前端页面转发与重定向

    /**
     * 转发前端页面
     */
    protected void transmitFrontPage(String pageName) {
        transmit("pages/store/" + pageName);
    }

    /**
     * 重定向前端页面
     */
    protected void redirectFrontPage(String pageName) {
        try {
            resp.sendRedirect(getFrontPage(pageName));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //endregion

    //region 后端页面转发与重定向

    /**
     * 转发后端页面
     */
    protected void transmitBackPage(String pageName) {
        transmit("pages/admin/" + pageName);
    }

    /**
     * 重定向后端页面
     */
    protected void redirectBackPage(String pageName) {
        try {
            resp.sendRedirect(getBackPage(pageName));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //endregion

    //endregion

    //region 得到地址
    protected String getPath() {
        return String.valueOf(this.getServletContext().getAttribute("base"));
    }

    /**
     * 得到前端地址
     */
    protected String getFrontPage(String pageName) {
        return String.valueOf(this.getServletContext().getAttribute("front")) + pageName;
    }

    /**
     * 得到后端地址
     */
    protected String getBackPage(String pageName) {
        return String.valueOf(this.getServletContext().getAttribute("back")) + pageName;
    }
    //endregion
}

其中一个子类

package com.sdllb.Servlet.IMP;
/**
 * @Author: 酷酷宅小明
 * @CreateTime: 2020-12-01 09:45
 */
@WebServlet("/yiLiaoYonPinType")
public class YiLiaoYonPinTypeServletIMP extends BaseServlet implements YiLiaoYonPinTypeServlet {
    private YiLiaoYongPinTypeService yiLiaoYonPinTypeService;
    private YiLiaoYonPinType yiLiaoYonPinType;

    @Override
    protected void setEntityAndSVC() {
        entity = new YiLiaoYonPinType();
        yiLiaoYonPinType = (YiLiaoYonPinType) this.entity;
        yiLiaoYonPinTypeService = new YiLiaoYongPinTypeServiceIMP();
    }

	/**
	添加
	*/
    @Override
    public void addYiLiaoYonPinType() {
        if (yiLiaoYonPinTypeService.addYiLiaoYongPinType(yiLiaoYonPinType)) {
            req.setAttribute("addYiLiaoYongPinTypeResult", true);
        } else {
            req.setAttribute("addYiLiaoYongPinTypeResult", false);
        }
        zfOrCdx("转发后端", "addYiLiaoYonPinType.jsp");
    }

	/**
	分页查询
	*/
    @Override
    public void limitYiLiaoYonPinType() {
        int thisPage = Integer.parseInt(req.getParameter("pageNo"));
        YiLiaoYonPinType_Page yiLiaoYonPinType_page = new YiLiaoYonPinType_Page(thisPage, 5);
        req.setAttribute("yiLiaoYonPinType_page", yiLiaoYonPinType_page);
        zfOrCdx("转发后端", "yiLiaoYonPinType_list.jsp");
    }

	/**
	删除
	*/
    @Override
    public void deleteYiLiaoYonPinType() {
        if (yiLiaoYonPinTypeService.removeYiLiaoYonPinType(yiLiaoYonPinType)) {
            req.setAttribute("deleteYiLiaoYonPinType", "OK");
        } else {
            req.setAttribute("deleteYiLiaoYonPinType", "NO");
        }
        zfOrCdx("转发后端", "yiLiaoYonPinType_list.jsp");
    }

	/**
	修改
	*/
    @Override
    public void updateYiLiaoYonPinType() {
        int id = Integer.parseInt(req.getParameter("Id"));
        if (yiLiaoYonPinTypeService.updateYiLiaoYongPinType(id, yiLiaoYonPinType)) {
            req.getSession().setAttribute("addYiLiaoYongPinTypeResult", true);
        } else {
            req.setAttribute("addYiLiaoYongPinTypeResult", false);
        }
        zfOrCdx("转发后端", "addYiLiaoYonPinType.jsp");
    }
	
	/**
	修改前根据Id查询
	*/
    @Override
    public void selectYiLiaoYonPinTypeById() {
        YiLiaoYonPinType yiLiaoYonPinTypeById = yiLiaoYonPinTypeService.findYiLiaoYonPinTypeById(yiLiaoYonPinType);
        req.setAttribute("yiLiaoYonPinType", yiLiaoYonPinTypeById);
        zfOrCdx("转发后端", "addYiLiaoYonPinType.jsp");
    }
}

你还别说: zfOrCdx(“转发后端”, “addYiLiaoYonPinType.jsp”);
这个方法用着是真的很爽,爽到爆的那种
(当然,也给我自己埋下一个隐患,我直到现在都不知道转发和重定向是怎么写的)

servlet项目中,sql生成器崩溃

直到现在,项目结构在控制层没出任何问题,但问题并不是出现在这里
原本以为我的sql生成器,在此刻一定能大展身手,好好表现一现一下
结果,它就像是张四贵的马一样,刚拉出来就给我直接人仰马翻,555555
之前说过一下,它单表简直是无敌的,

这次做的项目是一个医疗系统(分前台展示页面和后台管理员页面,而我的BaseServlet也是根据,这个项目设计出来的)

因为之前自己做的sql生成器,它只能操作单表,面对多表时,它几乎无能为力;
在当时,有另一种解决方案:一个查询分两次
如:
两个实体类,一个student与school,student里引用school
通过第一次查询出student,里面有school的Id,
再通过school的Id再去查询一次,并装入student对象里面
但我是拒绝的,虽然不是正式的项目,可我并不喜欢这种操作,
这样操作的话,student它的类结构就会变成这样:

怎么看都觉得非常别扭

public class Studnet{
	private int id;
	.....//其它属性
	private int schoolId;
	private School school;
}

所以我决定让我的sql生成器升级成可以操作多表的工具。
但我最终失败了,彻底失败了!!!
在测试sql生成器时,出现了内存泄露。就像递归一样地程序陷入死循环,
在那一个晚自习,我想尽各种办法,想让它停止递归,但都没有任何作用。
打算用计数器的方式关闭,但计数器属性的值,每次都会被覆盖,等于没设计数器。

在这种尴尬的情况下,如果说推倒之前写的sql生成器,
重写正常来写jdbc代码来实现,呵,但项目答辩迫在眉捷,时间已经来不及了。
也不知道哪里来的勇气,我也不清楚当时在想些什么。
结果就是抄起小破站,打开了MyBatis的视频:

sql生成器崩溃后的补救 --> MyBatis && Maven

于是在第二天晚自习,打开了小破站,开始学习MyBatis
很荣幸,一天的时间搞定知识点。
在MyBatis之前,并不知道 Maven,
只是当时视频里是这么做的,而我也跟着做,
直到第三天,Maven出问题了,
才知道还要去配阿里去镜象,啥是阿里去镜象?????
另外,MyBatis的多表操作,也是百度来的(视频里也并没有去教(快速入门的视频,不会教太多))


第三天晚自习成果:

package com.sdllb.service.root;

public class BaseService {
	//sqlSession工厂(整个项目中有一个足以)
    private static SqlSessionFactory factory;
    private SqlSession sqlSession;

    static {
        //这一段不用解释,就拿到sqlSession工厂
        try {
            String config = "mybatis.xml";
            InputStream in = Resources.getResourceAsStream(config);
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            factory = builder.build(in);
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
	
	/*从sqlsession工厂中得到sqlSession
	(本以为有时会用到这个方法,以防万一没设成private,但一直没有)
	*/
    public SqlSession getSqlSession() {
        sqlSession = null;
        if (factory != null) {
            sqlSession = factory.openSession(true);
        }
        return sqlSession;
    }
	
	/*
	得到Dao层的接口类
	*/
    public <T> T getDaoInterface(Class<T> daoInterface) {
        SqlSession sqlSession = this.getSqlSession();
        return sqlSession.getMapper(daoInterface);
    }

	/*
	事务提交
	*/
    public void commit() {
        sqlSession.commit(true);
    }
    
	/*
	关闭sqlSession
	*/
    public void close() {
        sqlSession.close();
    }
}

其中一个Service

package com.sdllb.service.IMP;

public class YiLiaoYongPinTypeServiceIMP implements YiLiaoYongPinTypeService {
	/*
	第一次时,使用的是继承BaseService,但还是出问题,
	具体出什么问题,已经不记得了,好像是session冲突,
	在之后,就改成了这种属性引入的方式
	*/
	//new BaseService对象
    private BaseService baseService = new BaseService();
    //通过BaseService对象,得到Dao接口,
    private YiLiaoYongPinTypeDao yiLiaoYongPinTypeDao = baseService.getDaoInterface(YiLiaoYongPinTypeDao.class);

    @Override
    public boolean addYiLiaoYongPinType(YiLiaoYonPinType yiLiaoYongPinType) {
        int i = yiLiaoYongPinTypeDao.addYiLiaoYonPinType(yiLiaoYongPinType);
        
        /*
        在这个位置,可以说,这一句是真的多余,
        如果没有这一句,的确是可以直接返回,
        (当时没有AOP的概念,只是在这次项目做完后,才知道的)
        */
        baseService.close();
        
        return i > 0;
    }

    @Override
    public boolean updateYiLiaoYongPinType(int id, YiLiaoYonPinType yiLiaoYonPinType) {
        int i = yiLiaoYongPinTypeDao.updateYiLiaoYonPinType(id, yiLiaoYonPinType);
        baseService.close();
        return i > 0;
    }

    @Override
    public List<YiLiaoYonPinType> findAllYiLiaoYongPinType() {
        List<YiLiaoYonPinType> yiLiaoYonPinTypes = yiLiaoYongPinTypeDao.selectAllYiLiaoYongPinType();
        baseService.close();
        return yiLiaoYonPinTypes;
    }

    @Override
    public List<YiLiaoYonPinType> findLimitYiLiaoYonPinType(int thisPage, int pageSize) {
        if (thisPage <= 0) thisPage = 1;
        int start = (thisPage - 1) * pageSize;
        List<YiLiaoYonPinType> yiLiaoYonPinTypes = yiLiaoYongPinTypeDao.limitYiLiaoYonPinTypeSelect(start, pageSize);
        yiLiaoYongPinTypeDao.limitYiLiaoYonPinTypeSelect(start, pageSize);
        baseService.close();
        return yiLiaoYonPinTypes;
    }

    @Override
    public int findYiLiaoYonPinTypeCount() {
        int i = yiLiaoYongPinTypeDao.selectYiLiaoYonPinTypeCount();
        baseService.close();
        return i;
    }

    @Override
    public boolean removeYiLiaoYonPinType(YiLiaoYonPinType yiLiaoYonPinType) {
        int i = yiLiaoYongPinTypeDao.deleteYiLiaoYonPinType(yiLiaoYonPinType);
        baseService.close();
        return i > 0;
    }

    @Override
    public YiLiaoYonPinType findYiLiaoYonPinTypeById(YiLiaoYonPinType yiLiaoYonPinType) {
        YiLiaoYonPinType newYiLiaoYonPinType = yiLiaoYongPinTypeDao.selectYiLiaoYonPinTypeByID(yiLiaoYonPinType.getYiLiaoYonPinTypeId());
        baseService.close();
        return newYiLiaoYonPinType;
    }
}

项目结束,整理公共代码,


常用的静态方法,等等,做成一个单独的项目,并用Maven打install,供其它项目共享,
至此,它成了我的,代码仓库,
我也给这个项目起了一个名字:lingDream
仅仅是因为我的名字里,有个“令”字;
或许,这名字总是感觉那么地别扭,但以我的英语水平,我也实在想不出什么更好的名字

SSM

在学习SSM时,倒也没什么要说的,
只是在写了一两个Mapper层的方法后,因为这是我正式写的第二次Mapper层方法,第一次是在上一个Servlet项目中,
此时,我就发现,每个Mapper类代码都有一个xXXInsert()的方法,当然,现在并没有像上次项目一样,时间紧迫,来不及思考。

尝试了一下,把前缀 xXX去了,insert()方法提到BaseMapper中,
记忆中当时可能是遇到了点问题,然后引入了泛型,来解决,

package ling.evidences.dao.root;

import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * 泛型 T 由子接口去声明
 * @Author: 酷酷宅小明
 * @CreateTime: 2020-12-26 14:24
 */
public interface BaseDao<T> {
    //region 查询

    /**
     * 查询所有
     */
    List<T> selectAll();

    /**
     * 根据Id查询
     */
    T selectById(T t);

    /**
     * 查询总共有多少条数据
     */
    int selectAllCount();

    /**
     * 分页查询
     */
    List<T> selectLimit(@Param("start") int start, @Param("pageSize") int pageSize);

    //endregion

    /**
     * 增加
     */
    int insert(T t);

    /**
     * 删除
     */
    int delete(T t);

    /**
     * 修改
     */
    int update(T oldS,T newS);
}

其中一个子类

package ling.evidences.dao;

import ling.evidences.dao.root.BaseDao;
import ling.evidences.entity.School;
/**
 * BaseDao 接口 的其中一个子接口,
 * 该接口中没有定义任何的抽象方法,
 * 唯一做的事只有声明一下泛型,
 * 如果说需要加什么特殊的方法,如abc(),
 * 需要考虑 abc() 是不是可以公用的,
 * 如果是,则直接加在BaseDao 接口中,
 * 如果不是,则放在该 接口中即可(加在此处,后期service需要做一次类型转换)
 * <School>
 * @Author: 酷酷宅小明
 * @CreateTime: 2020-12-25 10:47
 */
public interface SchoolDao extends BaseDao<School> {
	// 如果没有特殊情况,这里不用写任何一丁点的代码
}

BaseMapper写好,然后到了提BaseService,
(此时,两个决策上的失误,让我绕了一大圈)

package ling.evidences.service.root;

import java.util.List;

/**
 * @Author: 酷酷宅小明
 * @CreateTime: 2020-12-26 15:06
 */
public interface BaseService<T> {
    //region 查询
    /**
     * 查询所有
     */
    List<T> findAll();

    /**
     * 根据Id查询
     */
    T findById(T t);

    //endregion

    /**
     * 增加
     */
    boolean add(T t);

    /**
     * 删除
     */
    boolean remove(T t);

    /**
     * 修改
     */
    boolean modify(T oldT,T newT);
}

把分页相关的抽象方法,提取到另一个接口里

package ling.evidences.service.root;

import ling.evidences.tool.BasePage;

import java.util.List;

/**
 * 将分页模型提取出来
 * (分页模型需要一个Service)
 * @Author: 酷酷宅小明
 * @CreateTime: 2020-12-25 15:51
 */
public interface PageService<T> {
    /**
     * 查询总共有多少条记录
     */
    int findAllCount();

    /**
     * 分页查询
     */
    List<T> findLimit(int start, int pageSize);

    /**
     * 得到分页模型对象
     * 默认default 方法
     */
     default  BasePage<T> getBasePage() {
        return new BasePage<>(this);
    }
}

抽象的实现类,我给它起名:Transition,即桥梁的意思

package ling.evidences.service.root;

import ling.evidences.dao.root.BaseDao;

import java.util.List;

public abstract class Transition<T>
        implements PageService<T>, BaseService<T> {

	/**
	 * 无论是单表查询,还是多表查询,还是什么......
	 * 那么只要它属于service业务类,
	 * 那么它必定会有会有一个dao层的对象,
	 * 因为上面的每一步的操作,都重点突出在泛型上面,
	 * 此时,泛型的根本作用则完完全全地体现了出来!
	 */
    protected BaseDao<T> baseDao;

	/**
	 *
	 * 第一个决策失误:选择**setter**注入;
	 * 第二个决策失误:知道@Service,@AutoWrite注解,但都没有去用。
	 * 第一个失误,导致Service层,没法用@AutoWrite,它始终为Null,我一直都没明白。
	 * 虽然还可以用**构造器**注入,但在我当时的想法中,实在不想在看不惯,
	 * 子类service多出来的那一小段构造器,所以**构造器**注入的方式,在还没测试就被舍弃,
	 * 现在想来,不免有些后悔
	 * 最终结果:我的BeanXml配置文件,无限增长
	 *
	 * 以下是原注解:
	 *	 	 	 
	 * 因为一直以来都强调泛型,
	 * 所以即使是设置第一Dao层的方法可以直接提取出来,
	 * 而不用担心问题(泛型不匹配,运行时是直接报错的)
	 * (除非故意不去对应,每个节点的泛型)
	 */
    public void setBaseDao(BaseDao<T> baseDao) {
        this.baseDao = baseDao;
    }

    @Override
    public List<T> findAll() {
        return baseDao.selectAll();
    }

    @Override
    public T findById(T T) {
        return baseDao.selectById(T);
    }

    @Override
    public boolean add(T T) {
        return baseDao.insert(T) > 0;
    }

    @Override
    public boolean remove(T T) {
        return baseDao.delete(T) > 0;
    }

    @Override
    public boolean modify(T oldT, T newT) {
        return baseDao.update(oldT, newT) > 0;
    }

    @Override
    public int findAllCount() {
        return baseDao.selectAllCount();
    }

    @Override
    public List<T> findLimit(int start, int pageSize) {
        return baseDao.selectLimit(start, pageSize);
    }
}

其中一个子类

package ling.evidences.service.impl;

import ling.evidences.entity.School;
import ling.evidences.service.root.Transition;

/**
 * @Author: 酷酷宅小明
 * @CreateTime: 2020-12-25 10:47
 */
public final class SchoolServiceImpl extends Transition<School> {
	//这里如果没有特殊情况,是不用再去添加代码的
	//当然,多表查询属于特殊情况(下一个代码块处说明)
}

最终结果: 可用,但xml配置文件无限增长,且,对于SpringMvc中的控制层,没成功提取父类

SSM学得差不多,于是就写下了下面这篇文章:
点击文章标题
SSM 面向接口开发_实际开发中这样真的可以吗?

或许,
在现在回头看,它更应该叫
泛型化开发

写完SSM文章后,升级代码生成器

经过SSM的洗礼,我能感觉到,之前的代码生成器,由于只能生成Entity层,已经不再适用。

上次项目结束后,
我一个shift+delete删除这该死的sql生成器,差点坑死我。
但代码生成器,保留了下来,只不过它也需要做一次升级。
再像之前一样地一点一点地用StringBuffer来拼接,一定是不可能的。
毕竟,这一次摆在我面前的,并不是只有Entity一种 java文件
所以我需要找到一种技术,通过
模板 + 数据 => 结果
经过努力,
还真让我在小破站上找到了一个。
而我也在这个阶段,知道了有模板引擎这东西。
使用的技术:freemarker + jdbc元数据
缺点:freemarker所要求的后缀名为 .ftl
其它都还好,但缺点是,编辑器没给我引导提示,这我是一定能理解的,
但它报错是几份意思??更有的时候一个空指针异常,就直接把错误堆满整个文件…
额,,,,,好在能用,也解决了之前,只能生成entity层的缺点,暂时用着吧!
等以后,我学了其它技术,再回来收拾你!!

我的SSM与学校的Oracle(即将放寒假)

我在学着SSM时,学校老师教着Oracle数据库,但我没跟着,
虽然说,这并不是什么值得炫耀的事,
但当时我的想法是,如果公司需要用到的是Oracle,
我一定能在可控的时间内,搞定Oracle的知识点,并立即上手,干它
比起Oracle,我更需要的Spring,
更贴近于当时的心情,我需要的是AOP,
只不过在SSM学习中,我的收获,比我想象中更多,

假期

学完SSM,学校也迎来了放假,本打算在利用假期做个项目的,但琐事太多,一直都没有什么机会,直到开学,才有了机会,但此时,利用SSM来开发一个项目的心已经消磨贻尽。

收假后第一两个星期

最后一学期开学了,学校再一次地换了老师,教的是MyBatis,我听了一两节课,就没有再听。
想来,他们学完了Oracle数据库,而我用他们学Oracle的那段时间里,我学完了SSM。

虽然开学了一两个星期,但此时的我,却并没有急着去学Spring Boot,而是继续完善着我的代码生成器,以及,用SSM框架做着我的英语单词项目,
毕竟,放假在家里,我是没有足够的时间做这些的。


springBoot 阶段

说来,也是巧合,我进入SpringBoot阶段,是因有群里朋有的聊天,
它说,别用SSM了,用Spring Boot来做项目,很爽,
然后,我就打开了小破站,开始学Spring Boot。
别说,比起SSM真的更舒服。

《个人英语单词》项目(先用SSM做了一点点,改SpringBoot,再暂停)

英语单词项目:前期没建管理员相关的表的时候是八张表。此时的我并没有学习到Maven可以将一个项目分割成多个模块,并进行开发;
而在此时,面对一个一打开就是一长串的类,以及堆成一坨的页面文件,几乎是毫无办法,
唯一想到的办法则是,将页面的.html模板文件,(此时,已经用的thymeleaf模板引擎),使用文件夹分割开。

所有(八张)表{
	单词与中文相关{
		单词表:
			(该项目的根本)
			
		中文表:
			(中文翻译)
			
		单词与中文关系表:
			(每个英语单词对应的不会只有一个中文,一个中文不会只对应一个英语单词)
	}
	单词类型相关{
		单词类型表:
			(名词?副词?...)
			
		单词与类型关系表:
			(每个英语单词对应的不会只有一个类型,一个类型不会只对应一个英语单词)
			
	}
	单词标签相关{
		单词标签表:
			(人们总是喜欢把一堆又一堆东西进行打标签,再分类)
			
		单词与标签关系表:
			(一个单词可以被打上不同的标签,而一个标签可不是只属于一个单词)
			
		单词标签组表:
			(打的标签太多了,给它分一下组,就像文件夹一样)
	}
}

这样把页面用文件夹分类开解决问题了吗?其实并没有!!整个项目文件夹变得嵌套很深
比如:需要去找中文表的添加页面,
路径:
/admin/page/wordAndChinese/chinese/insertChinese.html

其中admin文件夹说的是管理员页面,
与page同级的是另一个文件夹,名为:adminPublic,这是一个装着被公共引用的页面零件

个人英语单词项目,就做到了这里,然后…

srpingBoot项目(一:(学校宿舍管理系统))

开学第三个星期,学校老师让我别再做,我的~~《英语单词项目》~~垃圾项目了,他给我找新的项目,让我来做。
说真的,如果他提前问我一下,我做的是什么项目,再让我别做,我会真的听话,但,这……
好吧,我承认,我最后还是把项目接了,条件是:我不再做任何作业,不再听课。

学校宿舍管理系统总共15张表

所有表{
	宿舍表:
		(整个项目的根本)
		
	宿舍分区表:
		(男生一楼,女生一楼......)
		
	宿舍状态表: 
		(因为学校的特殊情况,
		在这里写死了一部分东西,
		比如:宿舍淋浴头坏没坏,
		坏了打0,没坏打1)
		
	宿舍类型表:
		(男生宿舍、男老师宿舍、女生宿舍、女老师宿舍)
		
	宿舍卫生成绩表:
		(宿舍每隔一段时间都会评分)
		
	宿舍卫生成绩标题表:
		(它的作用就像 域名与Ip地址 一样,给每一期的评分作一个标题,好记住)
		
	班级表:
		(宿舍出问题,可以迅速查出这个宿舍归属于某个班级)
		
	管理员表:
		(登录)
		
	管理员类型表:
		(权限管理:1:普通账号,0:超级管理员账号)
		
	人员表:
		(所有的老师与学生都是人,之前试过,如果直接建一个教师表,并不容易控制,或者是因为能力尚浅)

	学生表:
		(学生表中,
		第一个字段为外键,即引用人员表,
		然后,第二个键为该学生所在的班级)
		
	人员类型表:
		(1:老师,0:学生)
		
	宿舍报修表:
		(报修时间,报修内容,报修备注)
		
	报修状态表:
		(是否修好,是否已报修等等)
}

继续学习着SpringBoot,偶然间,看到视频弹幕上,用MyBatis Plus,可以省掉很多spl语句,这样的一句话,让我想起以前我的sql生成器,自然而然地,我就找了篇MyBatis Plus的快速入门的视频来看。

MyBatisPlus(V:2.2)

MyBatis Plus同样地没看一两天就开始干了,或者说是,边看边做学校的项目。

在使用在使用MyBatisPlus时,我遇到了第一个问题:

处理分页
循环分页,页码

我总是习惯,先去往数据库里乱敲一堆数据在里边,反正是开发环境,然后把它查出来,显示出来;
已知:

Mybatis Plus中
	org.apache.ibatis.session.RowBounds类的对象作用是处理分页
	它下面有个子类:
		com.baomidou.mybatisplus.plugins.pagination.Pagination; //但我们并不用这个,
	再往它的子类:
		com.baomidou.mybatisplus.plugins.Page<T>; //其实相信到了这里,很多人都会选择用这一个
	但当我真正在做分页的时.....
	因为我用的是在Servlet时期学到的分页样式:
	每页最多显示5个页码:
	1
	1 2 
	1 2 3 
	1 2 3 4 
	1 2 3 4 5 
	2 3 4 5 6
	3 4 5 6 7
	(当存在有上一页是,显示上一页按扭,,存在下一页按钮,则显示下一页按钮)1】,2345 [下一页]
	[上一页] 1,【2】,345 
			12,【3】,45

在以前的JSP代码中,这些是靠JSTL的<C:SET/>来计算赋值,
但这段代码是从老师的文档中直接粘过来的,
这里教程里的老师故意留了一个坑,即,他只是考虑了总页数小于等于10的情况:
而在总页数超过10的时候,会有Bug

	<%--页码输出的开始--%>
	 <c:choose> 
	 	<%--情况 1:如果总页码小于等于 5 的情况,页码的范围是:1-总页码--%> 
	 	<c:when test="${ requestScope.page.pageTotal <= 5 }"> 
		 	<c:set var="begin" value="1"/> 
	 		<c:set var="end"value="${requestScope.page.pageTotal}"/>
	 	 </c:when>
	 	 <%--情况 2:总页码大于 5 的情况--%>
	 	 <c:when test="${requestScope.page.pageTotal > 5}">
		 <c:choose> 
			<%--小情况 1:当前页码为前面 3 个:123 的情况,页码范围是:1-5.--%> 
		 	<c:when test="${requestScope.page.pageNo <= 3}"> 
		 		<c:set var="begin" value="1"/> 
				<c:set var="end" value="5"/>
			</c:when>
			<%--小情况 2:当前页码为最后 3 个,8910,页码范围是:总页码减 4 - 总页码--%> 
			<c:when test="${requestScope.page.pageNo > requestScope.page.pageTotal-3}">
		  		<c:set var="begin" value="${requestScope.page.pageTotal-4}"/>
		   		<c:set var="end" value="${requestScope.page.pageTotal}"/> 
	   		</c:when>
	   		<%--小情况 34567,页码范围是:当前页码减 2 - 当前页码加 2--%> 	
	   		<c:otherwise>
				<c:set var="begin" value="${requestScope.page.pageNo-2}"/>
		 		<c:set var="end" value="${requestScope.page.pageNo+2}"/>
		 	</c:otherwise>
		  </c:choose> 
	</c:when>
</c:choose>

<c:forEach begin="${begin}" end="${end}" var="i">
	 <c:if test="${i == requestScope.page.pageNo}">${i}</c:if>
	  <c:if test="${i != requestScope.page.pageNo}">
	   <a href="${ requestScope.page.url }&pageNo=${i}">${i}</a> </c:if>
	    </c:forEach>
	    <%--页码输出的结束--%>
	     <%-- 如果已经 是最后一页,则不显示下一页,末页 --%> 
	     <c:if test="${requestScope.page.pageNo < requestScope.page.pageTotal}"> 
	    	 <a href="${ requestScope.page.url }&pageNo=${requestScope.page.pageNo+1}">下一页</a>
	    	 <a href="${ requestScope.page.url }&pageNo=${requestScope.page.pageTotal}">末页</a>
	    </c:if>${ requestScope.page.pageTotal }页,${ requestScope.page.pageTotalCount }条记录 
	   
	    到第<input value="${param.pageNo}" name="pn" id="pn_input"/><input id="searchPageBtn" type="button" value="确定"> 
	    
	    <script type="text/javascript"> $(function () {
		     // 跳到指定的页码 $("#searchPageBtn").click(function () {
		     	 var pageNo = $("#pn_input").val();
		     	 <%--var pageTotal = ${requestScope.page.pageTotal};--%> 
		     	 <%--alert(pageTotal);--%> 
		     	 // javaScript 语言中提供了一个 location 地址栏对象 
		     	 // 它有一个属性叫 href.它可以获取浏览器地址栏中的地址 
		     	 // href 属性可读,可写
				location.href = "${pageScope.basePath}${
					 requestScope.page.url }&pageNo=" + pageNo; 
					 });
				 }); 
		</script>

但到了thymeleaf中,虽然它也有一个与<c:set/>相似的th:with
但我试了很多次,结果都一样,第一次设置begin或end的值之后,
不记得当是是报错,还是没有像jsp一样,改变为最终的值,反正就是没用;

回头分析一下,其实我一直需要的是 beginend两个属性,用来作循环输出页码,
既然前端的thymeleaf无法计算,那么就让后端来计算输出

package com.lingDream.root.tool;

import com.baomidou.mybatisplus.plugins.Page;
import lombok.EqualsAndHashCode;

/**
 * 第一个版本中,是仿照上面JSP页面中的逻辑,
 * 直接来的,但后来,总页数只要超过10,
 * 会出现Bug,
 * 
 * 而这下面是最终修改过的,
 * 继承于Page<T>对象,
 * 并添加了两个属性
 * begin 和 end
 * 
 * @Author: 酷酷宅小明
 * @CreateTime: 2021-03-15 13:27
 */
@EqualsAndHashCode(callSuper = true)
public class MyPage<T> extends Page<T> {
    private Long begin;
    private Long end;

    public Long getBegin() {
        //默认情况
        begin = super.getCurrent()-2L;

        // 后三页
        if (super.getCurrent()>=super.getPages()-2) begin = super.getPages() -4;

        // 前三页
        if (super.getCurrent()<=3) begin = 1L;

        //特殊情况 总页数为4
        if (super.getPages() == 4) begin =1L;

        return begin;
    }


    public Long getEnd() {

        //默认情况
       end = super.getCurrent()+2L;
        // 如果是前三页
        if (super.getCurrent()<=3) end = 5L;

        // 如果是后三页
        if (super.getCurrent() >= super.getPages()-2) end = super.getPages();

        // 特殊情况 总页数为4
        if (super.getPages() == 4) end = 4L;

        return end;
    }

    public MyPage(int current, int size) {
        super(current, size);
    }
}

其实,即使到这样,它还是不够完美的,因为这是每页只显示5个页码的情况,
但如果我需要的是,每页显示6个页码呢?7个呢?
有没有一种算法,让它只需要传入你每页需要显示几个页码,
那么它就可以自动计算出beginend
或许是有的,但我没有去深究

到此,我从数据库中,利用MyBatis Plus取值,分页成功显示,

<!--region 分页-->
<!--/*@thymesVar id="page" type="com.lingDream.root.tool.MyPage<T>"*/-->
<nav aria-label="Page navigation" class="col-md-offset-4 ">
    <ul class="pagination">
        <li th:if="${page.hasPrevious()}">
            <a th:href="|@{/}${limitPath}=1|">
                <span>首页</span>
            </a>
            <a th:href="|@{/}${limitPath}=${page.current - 1}|" aria-label="Previous">
                <span aria-hidden="true">&laquo;</span>
            </a>
        </li>
        <li th:each="i:${#numbers.sequence(page.begin,page.end)}">
            <span th:if="${i==page.current}">【[[${i}]]】</span>
            <span th:if="${i!=page.current}">
                <a th:href="|@{/}${limitPath}=${i}|" th:text="${i}"></a>
            </span>
        </li>
        <li th:if="${page.hasNext()}">
            <a aria-label="Next" th:href="|@{/}${limitPath}=${page.current + 1}|"><span aria-hidden="true">&raquo;</span></a>
            <a th:href="|@{/}${limitPath}=${page.pages}|"><span>末页</span></a>
        </li>
        <li>共[[${page.pages}]]页,[[${page.total}]]条记录</li>
        <li class="page_nav_item">到第<input th:value="${page.current}" name="pn" id="pn_input"/><input id="searchPageBtn" type="button" value="确定"></li>
    </ul>
</nav>
<script type="text/javascript">
    $(function () {
        // 跳到指定的页码
        $("#searchPageBtn").click(function () {
            let pageNo = $("#pn_input").val();
            location.href = "[[|@{/}${limitPath}|]]=" + pageNo.trim();
        });
    });
</script>
<!--endregion-->
个性化定制显示数据

下一步,做宿舍表的分页展示数据,但我需要显示一下这个宿舍里面住着有多少个人。

第一种方案,分两次查询,(否决!)
	即先查询到当前宿舍,再根据当前宿舍的Id,再查询出这个宿舍的人数
第二种方案,修改Mapper.xml文件(成功一半)
	{
		com.hrxy.dormitory.mapper.ManagerMapper.selectPage
	} 
	原:
		Has been loaded by XML or SqlProvider,ignoring the injection of the SQL.
	翻译:
		已经被XML或SqlProvider加载,忽略了sQL的注入。
 就这一句话:证明了这第二种方案可行,
 	但问题是 分页查询时,最后需要limit #{start},#{pageSize}

到这一步,我卡了很久,很久,查阅了百度,官网都没能找到答案。
然后,看起了MyBatisPlus的源码(从BaseMapper类开始看,地毯式地查找),
遗憾的是,我并不能完全看得懂源码,所以也没能找到解决方法,
但这次阅读源码,也并不是完全一无所获,
我在源码中找到了
com.baomidou.mybatisplus.service.IService的一个接口,
以及它的实现类:
com.baomidou.mybatisplus.service.impl.ServiceImpl

意外的收获:ServiceImpl
public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {
....
...
}

接着就是把分页的事情先放到一边,百度起关于这两个类的资料:

原来如此,原来MyBatis Plus也同样做着一件相同的事,
把Mapper层,Service层,公共的代码放到父类。让子类Mapper、Service写少量,或不写代码

但我真正改写我的Service代码,继承于ServiceImpl代码时:

public class DormitoryService extends ServiceImpl<DormitoryMapper<Dormitory>,Dormitory>{
	/*
	这一句写下来,总觉得不舒服,我并不愿意这样写,
	我希望的效果是:
		public class DormitoryService extends ServiceImpl<Dormitory>{}
			毕竟这样是更加简洁的
	*/
}

原本计划是仿照MyPage一样的解决思路,
使ServiceImpl再往下继承一层,消除掉 泛型M
但到了真正写的时候,才发现,这是不可能的!根本不可能

原本以为只是我的版本低了点,试过将MyBatis Plus升级 3.X 的版本,
但依然,没有改变,

最后,一狠心,把ServiceImpl的代码,复制下来,改名为MyService,

但一开始,我并没有像上面一样,使用构造函数来弄,也是同样地按照

setBaseMapper(MyMapper<T> baseMapper){this.baseMapper = baseMapper}

这样的方法,但它一直在报错

有些记不清,当时报的是空指针异常:baseMapper为null
还是匹配到多个Bean,导致冲突,项目都没运行成功

还好没放弃,我把Setter注入,换成了构造器注入,让子类继承时,必须去实现它的构造函数。
运行成功!

这时候我才明白,所谓的@Autowired,执行的时机是在构造方法之后,set注入的方式,而我选择用构造函数,让子类来定义泛型<T>,则避免了这个问题。

回忆起SSM阶段,如果当时,我选的就是构造函数注入,
或许,就不用绕这么一个大圈!!!

回头再次解决limit参数问题

第一次尝试,把limit写死,成功运行,也让我找到了规律

<select id="selectPage" resultType="返回类型">
        select 需要的列 from 表名
        limit 1,2
        <!--
        虽然没真正的显法sql语句,但从页面结果反推可知:
        MyBatis Plus自动在我的limit 1,2后,又加了一次limit。
        最终语句:
        select 需要的列 from 表名
        limit 1,2 limit 参数1,参数2
        -->
</select>

然后,删了limit,成功实现偷梁换柱

<select id="selectPage" resultType="返回类型">
        select 需要的列 from 表名
</select>

至此,有了这篇文章
点击文章标题
spring boot泛型化编程(适用于spring)

Maven分模块

Maven分模块,也是在这一时期学的,起因是因为一个同班同学和我说的,当然,也只是提示,
最终还是百度,但百度查来的,并不能直接用,而是需要自己改动一下。

maven模块划分(个人风格,不完美,未来会改进)

当然,这样的模块划分,我还是不满意:

当随着模块的增加,这一块文件夹下来,肯定是又臭又长,但起码现阶段可用,也就没继续深研了!

至此,《学校宿舍管理系统》看似完结,源代码,war包,文档说明也交给了老师。

直到后来我做另一个项目,才发现,spring boot 打war包,在本地windows系统中,tomcat能运行,但linux中,不知为何,同样的版本,就是运行不了,但项目交给学校老师,事情过了一个多月,他们都没有开始部署

当然,在做这个《学校宿舍管理系统》时,
我也不会忘记,继续完善我的代码仓库

我也要回过头来,继续写我的《个人英语单词项目》了

继续写《个人英语单词》项目

再次来编写《个人英语单词》项目时,第一步,当然是把项目分成多个Maven模块,这样分出来确实是比之前好很多,也更方便。
当然,利用一下Maven的特性,当某个项目依赖于其它某个项目时,它会把它依赖的jar包一同导入,
所以主配置文件只需要加上我的仓库依赖即可。

反向思维,共用页面

前置说明:(根据我的个人情况)
在后台管理系统中,几乎所有的Controller都会有的请求方法:
1:分页的请求
(1)去到新增页面的请求
(2)去到修改页面
(3)添加或修改的结果页面


2:个人前期情况
(1)一个类模块的添加和修改,共用为一个页面
(2)所有类模块的添加和修改的结果页面,共用为一个页面


3:需要的知识准备
(1)thymeleaf引入页面的操作方法
(2)往Model对象中装数据
(3)反射获取类名

BaseController接口

package com.lingDream.root.controller;

/**
 * @Author: 酷酷宅小明
 * @CreateTime: 2021-04-21 10:37
 */
public interface BaseController<T> {
    @RequestMapping({"/getPage"})
    String getPage(Model model, String val, String filter, Integer thisPage);

    @RequestMapping(
        value = {"/add"},
        method = {RequestMethod.POST}
    )
    String add(HttpServletRequest request, T entity, Model model);

    @RequestMapping(
        value = {"/delById"},
        method = {RequestMethod.POST}
    )
    @ResponseBody
    String delById(HttpServletRequest request, T entity);

    @RequestMapping({"/updateFindById"})
    String updateFindById(HttpServletRequest request, T entity, Model model);

    @RequestMapping(
        value = {"/update"},
        method = {RequestMethod.POST}
    )
    String update(HttpServletRequest request, T entity, Model model);

    @RequestMapping({"/toInsertPage"})
    String toInsertPage(Model model);
}

抽象类:MyController:定义构造规则,以及一些公用的方法,
ControllerPageConfig :定义的页面路径对象

package com.lingDream.root.controller;

/**
 * @Author: 酷酷宅小明
 * @CreateTime: 2021-04-21 10:37
 */
public abstract class MyController<T> implements BaseController<T> {
    @Autowired
    protected ControllerPageConfig controllerPageConfig;
    protected final BaseService<T> service;
    protected final String COMMENT;

    public MyController(BaseService<T> service, String COMMENT) {
        this.service = service;
        this.COMMENT = COMMENT + " →";
    }

    protected MyPage<T> getMyPage(Integer thisPage) {
        return this.service.selectPage(new MyPage(thisPage, 10), (Wrapper)null);
    }

    protected MyPage<T> getMyPage(Integer thisPage, Integer pageSize) {
        return this.service.selectPage(new MyPage(thisPage, pageSize), (Wrapper)null);
    }

    protected MyPage<T> getMyPage(Integer thisPage, Wrapper<T> wrapper) {
        return this.service.selectPage(new MyPage(thisPage, 10), wrapper);
    }
	
	/**
	 *页面中的${title}不需要最后的 " →"
	 * 要不然显示出来会很别扭
	 * <title th:text="${title}"></title>
	 */
    protected void setTitle(Model model) {
        String substring = this.COMMENT.substring(0, this.COMMENT.indexOf(" →"));
        model.addAttribute("title", substring);
    }

    protected void setRequestURL(HttpServletRequest request, Model model) {
        String requestURL = request.getRequestURL().toString();
        String[] split = requestURL.split("/");
        String classRequestMapping = split[split.length - 2];
        model.addAttribute("toPage", classRequestMapping);
    }

    protected Wrapper<T> getWrapper(String val, String filter) {
        Wrapper<T> wrapper = new EntityWrapper();
        wrapper.like(filter, val);
        return wrapper;
    }
	
	/**
	 * 分页时使用的:val与filter用作模糊查询的参数
	 */
    protected void setPageTitleAndPartAddress(Model model, String val, String filter) {
        this.setTitle(model);
        String partAddress = this.getClass().getSimpleName();
        //就是页面零件的地址,通过反射获取小写类名
        partAddress = StringUtils.lowFirstChar(partAddress.substring(0, partAddress.length() - 10));
        model.addAttribute("partAddress", partAddress);
        String limitPath = partAddress + "/getPage?";
        if (!Objects.isNull(val)) {
            limitPath = limitPath + "val=" + val + "&";
        }

        if (!Objects.isNull(filter)) {
            limitPath = limitPath + "filter=" + filter + "&";
        }

        limitPath = limitPath + "thisPage";
        model.addAttribute("limitPath", limitPath);
    }
}

抽象类ControllerImpl:实现一下BaseController


package com.lingDream.root.controller;

/**
 * @Author: 酷酷宅小明
 * @CreateTime: 2021-04-21 10:37
 */
public abstract class ControllerImpl<T> extends MyController<T> {
    public ControllerImpl(BaseService<T> service, String COMMENT) {
        super(service, COMMENT);
    }

    public String getPage(Model model, String val, String filter, Integer thisPage) {
        if (thisPage == null) {
            thisPage = 1;
        }

        Wrapper<T> wrapper = this.getWrapper(val, filter);
        MyPage<T> myPage = this.getMyPage(thisPage, wrapper);
        model.addAttribute("page", myPage);
        this.setPageTitleAndPartAddress(model, val, filter);
        return this.controllerPageConfig.getListPage();
    }

    public String toInsertPage(Model model) {
	    /*判断model中存储的信息长度,
	    设置出路径(添加/修改),
	    此方法应该放在第一行
	    */
        String path = model.asMap().size() == 0 ? "add" : "update";
        model.addAttribute("path", path);
        
        this.setTitle(model);
        String partAddress = this.getClass().getSimpleName();
        partAddress = partAddress.substring(0, partAddress.length() - 10);
        model.addAttribute("partAddress", StringUtils.lowFirstChar(partAddress));
        return this.controllerPageConfig.getInsertPage();
    }

    public String add(HttpServletRequest request, T entity, Model model) {
        super.setRequestURL(request, model);
        model.addAttribute("result", this.COMMENT + "添加成功");
        if (!this.service.insert(entity)) {
            model.addAttribute("result", this.COMMENT + "添加失败");
        }

        return this.controllerPageConfig.getResultPage();
    }

    public String delById(HttpServletRequest request, T entity) {
        return this.service.deleteById((Serializable)entity) ? this.COMMENT + "删除成功" : this.COMMENT + "删除失败";
    }

    public String updateFindById(HttpServletRequest request, T entity, Model model) {
        T t = this.service.selectById((Serializable)entity);
        model.addAttribute("entity", t);
        return this.toInsertPage(model);
    }

    public String update(HttpServletRequest request, T entity, Model model) {
        super.setRequestURL(request, model);
        model.addAttribute("result", this.COMMENT + "修改成功");
        if (!this.service.updateById(entity)) {
            model.addAttribute("result", this.COMMENT + "修改失败");
        }

        return this.controllerPageConfig.getResultPage();
    }
}

真正的子类(一)

package com.lingDream.llfEnglish.controller;

/**
 * @Author: 酷酷宅小明
 * @CreateTime: 2021-04-21 10:37
 */
@Controller
@RequestMapping(value = "/chinese")
public class ChineseController extends ControllerImpl<Chinese> {
    public ChineseController(MyService<Chinese> service) {
        super(service, "中文组词");
    }
}

真正的子类(二)

package com.lingDream.llfEnglish.controller;

/**
 * @Author: 酷酷宅小明
 * @CreateTime: 2021-04-21 10:37
 */

@Controller
@RequestMapping(value = "/word")
public class WordController extends ControllerImpl<Word> {

    public WordController(MyService<Word> service) {
        super(service, "英语单词");
    }

}

分页查询的页面:list.html

<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf">
<head>
    <th:block th:replace="admin/adminPublic/links.html"/>
    <title th:text="|${title}管理|"></title>
</head>
<body>
<!--region main 容器-->
<div class="container-fluid">

    <!--region 顶部导航菜单-->
    <nav class="navbar navbar-inverse navbar-fixed-top" th:include="admin/adminPublic/adminHeader.html"></nav>
    <!--endregion-->

    <hr>

    <!--region 内容区域-->

    <div class="row">

        <!--region 左侧导航栏-->
        <div class="col-md-2 sidebar sub-menu col-sm-3" th:include="admin/adminPublic/adminLeft.html"></div>
        <!--endregion-->

        <!--region 内容显示区域-->
        <div class="col-md-offset-2 col-md-10 col-sm-9 col-sm-offset-3" style="padding: 20px">

            <!--region 导航器-->
            <ol class="breadcrumb">
                <li><a th:href="@{/toIndexPage}">首页</a></li>
                <li class="active" th:text="|${title}管理|"></li>
            </ol>
            <!--endregion-->

            <!--region 模糊查询-->
            <!--<div th:if="${showPart}" class="row"
                 th:include="|/admin/${partAddress}/list/search-wrap.html|"></div>-->
            <!--endregion-->

            <br/>

            <!--region 操作菜单-->
            <ul class="breadcrumb">
                <li><a th:href="|@{/}${partAddress}/toInsertPage|" th:text="|新增${title}|"></a></li>
            </ul>
            <!--endregion-->

            <!--region 表格-->
            <!--region partAddress:就是通过反射获到到的-->            
            <table th:include="|admin/${partAddress}_list.html|" class="table table-bordered table-striped table-hover table-condensed text-center"></table>
            <!--endregion-->

            <!--region 分页模型-->
            <div th:include="|admin/adminPublic/adminLimit.html|">
            </div>
            <!--endregion-->

        </div>
        <!--endregion-->
    </div>

<!--endregion-->

   <!--region 底部信息-->
<div class="row">
    <div></div>
</div>
<!--endregion-->

</div>
<!--endregion-->
</body>
</html>

增加或修改的页面:inser.html

<!DOCTYPE html>
<html lang="zh-CH" xmlns:th="http://www.thymeleaf">
<head>
    <th:block th:replace="admin/adminPublic/links.html"/>
    
    <title th:text="|${'add'.equals(path)?'新增':'修改'}${title}|"></title>
</head>
<body>
<!--region main 容器-->
<div class="container-fluid">
    <!--region 顶部导航菜单-->
    <nav class="navbar navbar-inverse navbar-fixed-top" th:include="admin/adminPublic/adminHeader.html"></nav>
    <!--endregion-->

    <hr>
    <!--region 内容区域-->

    <div class="row">
        <!--region 左侧导航栏-->
        <div class="col-md-2 sidebar" th:include="admin/adminPublic/adminLeft.html"></div>
        <!--endregion-->

        <!--region 内容显示区域-->
        <div class="col-md-offset-2 col-md-10" style="padding:50px 100px 0">

            <!--region 导航器-->
            <ol class="breadcrumb">
                <li><a th:href="@{/toIndexPage}">首页</a></li>
                <li><a th:href="|@{/}${partAddress}/getPage?thisPage = 1|" th:text="|${title}管理|"></a></li>
                <li class="active" th:text="|${'add'.equals(path)?'新增':'修改'}${title}|"></li>
            </ol>
            <!--endregion-->

            <hr>

            <!--region 表单-->
            <form th:action="|@{/}${partAddress}/${path}|" method="post" class="form-horizontal" role="form">
                <th:block th:include="|admin/${partAddress}_add.html|"/>
                <div class="form-group">
                    <div class="col-md-offset-1 col-md-2">
                        <input value="提交" type="submit" class="btn btn-primary form-control">
                    </div>
                    <div class="col-md-2">
                        <input onClick="history.go(-1)" type="button" class="btn btn-info form-control" value="返回"/>
                    </div>
                </div>
            </form>
            <!--endregion-->
        </div>
        <!--endregion-->
    </div>

    <!--endregion-->

    <!--region 底部信息-->
    <div class="row"></div>
</div>
<!--endregion-->
<!--endregion-->
</body>
</html>

结果页面:addResult.html

<!DOCTYPE html>
<html lang="zh-CN"  xmlns:th="http://www.thymeleaf">
<head>
    <links th:replace="admin/adminPublic/links.html"></links>
    <link th:href="@{/css/style.css}" type="text/css" rel="stylesheet">
    <title>结果</title>
</head>
<body>
<div class="one">
    <!-- #region Two -->
    <div class="two">
        <!-- #region 提示文字 -->
        <div class="tx">
            <p>
                <span th:text="${result}" style="font-size: 40px"></span>
            </p>
        </div>
        <!-- #endregion 提示文字-->
        <div class="wi"></div>
        <!-- #region but -->
        <div class="but_left">
            <button class="button" style="vertical-align:middle"><span><a
                    th:href="|@{/}${toPage}/toInsertPage|">返回添加页面</a> </span></button>
        </div>
        <div class="but_right">
            <button class="button" style="vertical-align:middle"><span><a th:href="|@{/}${toPage}/getPage?thisPage=1|">返回列表页</a> </span>
            </button>
        </div>
    </div>
    <!-- #endregion but-->
</div>
<!-- #endregion Two -->
</body>
</html>

缩减代码:chinese_list

<tr>
    <th>序号</th>
    <th>中文组词</th>
    <th>中文备注</th>
    <th>操作</th>
</tr>
<!--/*@thymesVar id="item" type="com.lingDream.llfEnglish.entity.Chinese"*/-->
<tr th:each="item:${page.records}">
    <td th:text="${itemStat.count}"></td>
    <td th:text="${item.chineseInfo}"></td>
    <td th:text="${item.chineseComment}"></td>
    <td>
        <a class="link-update"
           th:href="|@{/}${partAddress}/updateFindById?chineseId=${item.chineseId}|">修改</a>
        <a th:onclick="|delAjax({chineseId:${item.chineseId}})|" href="#" class="link-del">删除</a>
    </td>
</tr>

缩减代码 chinese_add

<!--region 中文组词Id-->
<div class="form-group" th:if="${entity!=null}">
    <div class="col-md-2">
        <label>中文组词Id</label>
    </div>
    <div class="col-md-5">
        <input type="hidden" name="chineseId" class="form-control"
               th:value="${entity?.chineseId}">
        <div th:text="${entity?.chineseId}"></div>
    </div>
</div>
<!--endregion-->

<!--region 中文组词信息 -->
<div class="form-group">
    <div class="col-md-2">
        <label for="chineseInfo">
            <i class="text-danger h3">*</i>中文组词信息:
        </label>
    </div>
    <div class="col-md-5">
        <input id="chineseInfo" class="form-control"
               name="chineseInfo"
               th:value="${entity?.chineseInfo}" size="50" type="text">
    </div>
</div>
<!--endregion-->

<!--region 中文组词备注-->
<div class="form-group">
    <div class="col-md-2"><label for="chineseComment">中文组词备注:</label></div>
    <div class="col-md-5">
        <textarea name="chineseComment" id="chineseComment" class="form-control" cols="30"
                  rows="10" placeholder="请输入中文组词备注"
                  th:text="${entity?.chineseComment}"></textarea>
    </div>
</div>
<!--endregion-->

word_list,word_add:不想粘了

自定义的配置文件

# 总文件
lingDream:
  controller:
    listPage: admin/index/list
    insertPage: admin/index/insert
    resultPage: admin/adminPublic/addResult
    aspect: true
  service:
    aspect:
      update: true
      insert: true
#SpringBoot 的总配置文件

#激活开发环境
#
spring:
  profiles:
    active: dev

#激活测试环境
#spring.profiles.active=test

#激活生产环境
#spring:
#  profiles:
#    active: product


在曾经一次和老师的交谈中,听老师说,有的公司用的是iframe来分割页面,但我现在发现了新大陆。
不能说iframe不好,只是iframe方式更简单一些,但后期的维护,在我的认为中并不是非常方便,
但我发现的这个,前期的项目搭建,确实不是一件容易的事,但后期的扩展,却非常容易。

github & linux

github 与 linux的学习,可能最早应该追溯到,jdbc之前,但一直都是以试着玩的心态去用,
所以一直都没什么进展。遇到问题,查百度

更多推荐

个人开发经历--我的java学习之路(学校篇)

本文发布于:2023-03-31 15:04:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/41c4280881fd725cffc6da7665d7fbce.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:之路   学校   java

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!