个人开发经历--我的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.com/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;//自动配置的entityprotected Object entity;//图片(文件上传时用到的)protected TuPian tuPian = new TuPian();//管理员()private Manager manager;//这个由子类去实现,通过这个方法,可以让父类知道entity是谁,等等信息abstract protected void setEntityAndSVC();//region  doGet与doPost@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) {try {setInfo(req, resp);getActive();} catch (Exception e) {e.printStackTrace();}}@Overrideprotected 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");//设置当前操作的实体对象entityMap 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));//回查最新的一张图片Idint lastTuPianId = lastTuPianId().getTuPianId();//将最后一张图片的id交给valval = 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;@Overrideprotected void setEntityAndSVC() {entity = new YiLiaoYonPinType();yiLiaoYonPinType = (YiLiaoYonPinType) this.entity;yiLiaoYonPinTypeService = new YiLiaoYongPinTypeServiceIMP();}/**添加*/@Overridepublic void addYiLiaoYonPinType() {if (yiLiaoYonPinTypeService.addYiLiaoYongPinType(yiLiaoYonPinType)) {req.setAttribute("addYiLiaoYongPinTypeResult", true);} else {req.setAttribute("addYiLiaoYongPinTypeResult", false);}zfOrCdx("转发后端", "addYiLiaoYonPinType.jsp");}/**分页查询*/@Overridepublic 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");}/**删除*/@Overridepublic void deleteYiLiaoYonPinType() {if (yiLiaoYonPinTypeService.removeYiLiaoYonPinType(yiLiaoYonPinType)) {req.setAttribute("deleteYiLiaoYonPinType", "OK");} else {req.setAttribute("deleteYiLiaoYonPinType", "NO");}zfOrCdx("转发后端", "yiLiaoYonPinType_list.jsp");}/**修改*/@Overridepublic 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查询*/@Overridepublic 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);@Overridepublic boolean addYiLiaoYongPinType(YiLiaoYonPinType yiLiaoYongPinType) {int i = yiLiaoYongPinTypeDao.addYiLiaoYonPinType(yiLiaoYongPinType);/*在这个位置,可以说,这一句是真的多余,如果没有这一句,的确是可以直接返回,(当时没有AOP的概念,只是在这次项目做完后,才知道的)*/baseService.close();return i > 0;}@Overridepublic boolean updateYiLiaoYongPinType(int id, YiLiaoYonPinType yiLiaoYonPinType) {int i = yiLiaoYongPinTypeDao.updateYiLiaoYonPinType(id, yiLiaoYonPinType);baseService.close();return i > 0;}@Overridepublic List<YiLiaoYonPinType> findAllYiLiaoYongPinType() {List<YiLiaoYonPinType> yiLiaoYonPinTypes = yiLiaoYongPinTypeDao.selectAllYiLiaoYongPinType();baseService.close();return yiLiaoYonPinTypes;}@Overridepublic 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;}@Overridepublic int findYiLiaoYonPinTypeCount() {int i = yiLiaoYongPinTypeDao.selectYiLiaoYonPinTypeCount();baseService.close();return i;}@Overridepublic boolean removeYiLiaoYonPinType(YiLiaoYonPinType yiLiaoYonPinType) {int i = yiLiaoYongPinTypeDao.deleteYiLiaoYonPinType(yiLiaoYonPinType);baseService.close();return i > 0;}@Overridepublic 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;}@Overridepublic List<T> findAll() {return baseDao.selectAll();}@Overridepublic T findById(T T) {return baseDao.selectById(T);}@Overridepublic boolean add(T T) {return baseDao.insert(T) > 0;}@Overridepublic boolean remove(T T) {return baseDao.delete(T) > 0;}@Overridepublic boolean modify(T oldT, T newT) {return baseDao.update(oldT, newT) > 0;}@Overridepublic int findAllCount() {return baseDao.selectAllCount();}@Overridepublic 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个页码:11 2 1 2 3 1 2 3 4 1 2 3 4 5 2 3 4 5 63 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;//特殊情况 总页数为4if (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();// 特殊情况 总页数为4if (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})@ResponseBodyString 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> {@Autowiredprotected 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.org">
<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.org">
<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.org">
<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><ath: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/listinsertPage: admin/index/insertresultPage: admin/adminPublic/addResultaspect: trueservice:aspect:update: trueinsert: true
#SpringBoot 的总配置文件#激活开发环境
#
spring:profiles:active: dev#激活测试环境
#spring.profiles.active=test#激活生产环境
#spring:
#  profiles:
#    active: product


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

github & linux

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

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

  1. JAVA学习之路--基础篇三

    目录 关于Java中从键盘输入的语句 nextxxx().next().nextLine()的区别 语句 if和if else语句 Switch语句 for语句 while和do..while bre ...

  2. Java学习之路---对象篇(Object)

    什么是对象? Java是一种面向对象的语言,那么什么是对象呢?我们将问题空间中的元素及其在解空间中的表示称之为"对象". "万物皆对象":将对象视为一个变量,可 ...

  3. 测试学开发——第一课:java学习路程

    测试学开发--第一课:java学习路程

  4. java学习之路目录(已完结)

    java学习之路目录(持续更新中-) 第一阶段 javaSE(完结) 序号 标题 内容 001 java初识 java语言特点.体系结构.运行机制 002 java SE基础语法 注释.关键字.变量. ...

  5. 我的Java学习之路2009-11-17

    -------------------------------2009年3月19日开始----------------------------- 下载JDK Myeclipse Netbeans JB ...

  6. JAVA学习之路:不走弯路,就是捷径(一)

      0.引言 在ChinaITLAB导师制辅导中,笔者发现问得最多的问题莫过于"如何学习编程?JAVA该如何学习?".类似的问题回答多了,难免会感觉厌烦,就萌生了写下本文的想法.到 ...

  7. Java学习之路1——安装JDK1.8||安装idea2022||Java项目创建【重拾Java】

    Java学习之路1--安装JDK1.8||安装idea2022[重拾Java] 前言 安装 安装JDK1.8 安装idea2022(JetBrains Toolbox) Java项目创建 创建 项目结 ...

  8. 菜鸟haqima的Java学习之路第一天

    菜鸟haqima的Java学习之路第一天 导读:DOS命令 常用快捷键 Java的简单概述 (第一章 Java开发环境的搭建) 1.常用的DOS命令 1.1.怎么打开DOS命令窗口 win键+r(组合 ...

  9. java学习之路之javaSE基础1

    <h2>java学习之路之javaSE基础1</h2> <div> ###01.01_计算机基础知识(计算机概述)(了解) * A:什么是计算机?计算机在生活中的应 ...

最新文章

  1. Python3序列解包
  2. idea 快捷键整理
  3. 如何在Angular项目里创建新的Service
  4. 生成方法中参数的注释
  5. Android 多选列表
  6. 【MySQL】MySQL数据库SQL优化工具 SQL Tuning Expert for MySQL(收费)
  7. MUI+Htmlplus开发APP实现页面之间传值
  8. [Windows] 集福宝- 支付宝 集福神器 2019
  9. 怎么去掉字符串最后一个逗号
  10. 运算符优先级(cpp/c)
  11. 5安卓输入法键盘显示 搜索_日语输入法怎么用?
  12. jq json格式化工具
  13. trend函数用oracle实现,Excel函数TREND函数的用法
  14. 报童模型仿真,运行不出来,哪儿出问题了
  15. 国产龙芯笔记本(on 龙芯2F)的使用体验
  16. 小布语音下载安装_小布语音助手
  17. 程序人生 - 如何绘制二维码?
  18. 使用Arduino开发板进行语音识别
  19. 【干货】Python:load_workbook用法(持续更新)
  20. fieldOfView

热门文章

  1. android动画机制,动画机制-《Android群英传》
  2. python自动登录校园网_python 脚本自动登陆校园网
  3. Python_xlwings小技巧(range,带格式复制粘贴)
  4. Windows Server 2008 显示隐藏文件 扩展名
  5. 2021 年情人节最新的表白神器(Python 制作,源码已开放)
  6. Markdown在线写作速成
  7. 2022互联网技术与应用博览会|2022年深圳互联网技术展IDWF
  8. [C#问题]WebBrowser在Form程序中使用的感想。
  9. jpg转pdf转换器 注册码
  10. The package jxl is not accessible解决