JAVA WEB快速入门系列之前的相关文章如下:(文章全部本人【梦在旅途原创】,文中内容可能部份图片、代码参照网上资源)
今天是第五篇,也是该系列文章的最后一篇,接上篇《JAVA WEB快速入门之从编写一个基于SpringMVC框架的网站了解Maven、SpringMVC、SpringJDBC》,通过上篇文章的详细介绍,知道如何使用maven来快速构建spring MVC应用,也能够使用spring MVC+springJDBC实现网站开发,而本文所涉及的知识则是在这基础之上继续提升,核心是讲解如何使用spring boot来更快速的构建spring MVC,并通过mybatis及代码生成相关DAO,同时利用VUE前端框架开发前后端分离的网站,用户体验更好,废话不多说,直接进入本文主题。
(提示:本文内容有点长,涉及的知识点也比较多,若是新手建议耐心看完!)
一、创建Spring Boot+SpringMVC空项目
1.1通过https://start.spring.io/官网快速生成一个Spring Boot+SpringMVC空项目,如下图示:
设置后点击页面的生成项目按钮,即可生成并下载spring boot项目代码压缩包,然后使用IDE导入存在的maven project即可。
1.2调整项目,解决一些踩坑点
1.2.1.调整spring boot App启动类(如:SpringbootdemoApplication)到根包目录或在启动类上显式添加@ComponentScan注解,并指定包路径,如下代码所示,cn.zuowenjun.boot是根包目录,其余都是cn.zuowenjun.boot的子包
package cn.zuowenjun.boot;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;//import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication//指定为Spring Boot启动入口,内含多个spring所需要的注解
@MapperScan(basePackages="cn.zuowenjun.boot.mapper")//设置Mybaits扫描的mapper包路径//@ComponentScan(basePackages= {"cn.zuowenjun.controller"})//如果不在根包目录,则需指定spring管理的相关包路径
@EnableTransactionManagement //启动事务管理
public classSpringbootdemoApplication {public static voidmain(String[] args) {
SpringApplication.run(SpringbootdemoApplication.class, args);
}
}
1.2.2.解决POM文件报:
Description Resource Path Location Type
Execution default-resources of goal org.apache.maven.plugins:maven-resources-plugin:3.1.0:resources failed: Unable to load the mojo 'resources' (or one of its required components) from the plugin 'org.apache.maven.plugins:maven-resources-plugin:3.1.0'
直接在POM中添加如下resources依赖:
org.apache.maven.plugins
maven-resources-plugin
2.5
maven-plugin
1.2.3.设置热编译启动模式,以便可以随时更改代码后即时生效
org.springframework.boot
spring-boot-devtools
true
org.springframework.boot
spring-boot-maven-plugin
true
设置后项目的视图就有如下显示效果:
1.3演示请求REST API分别返回JSON、XML
创建好spring boot空项目环境后,我们就可以开始编写相关代码了,在此仅贴出实现了REST API分别响应返回JSON、XML格式的Controller,实现步骤如下:
1.3.1在cn.zuowenjun.boot.controller包中创建DemoController,并编写hellojson、helloxml Action方法,代码如下:
packagecn.zuowenjun.boot.controller;import org.springframework.web.bind.annotation.*;import cn.zuowenjun.boot.domain.*;
@RestControllerpublic classDemoController {
@RequestMapping(value="/hello/json",produces="application/json;charset=utf-8")publicHelloDto hellojson()
{
HelloDto dto=newHelloDto();
dto.setMessage("hello,zuowenjun.cn,hello java spring boot!");returndto;
}
@RequestMapping(value="/hello/xml",produces="text/xml;charset=utf-8")publicHelloDto helloxml()
{
HelloDto dto=newHelloDto();
dto.setMessage("hello,zuowenjun.cn,hello java spring boot!");returndto;
}
}
如上代码简要说明:@RestController相当于是:@Controller、@ResponseBody,这个可以查看@RestController注解类代码就知道;@RequestMapping指定请求映射,其中produces设置响应内容格式(可理解为服务端是生产者,而用户在浏览器端【客户端】是消费端),还有consumes属性,这个是指可接收请求的内容格式(可理解为用户在浏览器端发送请求是消息的生产者,而服务端接收并处理该请求为消息的消费者),当然还有其它一些属性,大家可以参见我上篇文章或网络其它大神的相关文章加以了解。
另外需要注意,默认spring MVC只返回JSON格式,若需返回XML格式,还需添加XML JAR包依赖,如下:(可以看到version这里我指定了版本号区间,表示2.5.0及以上版本都可以,有些依赖spring-boot-starter-parent中都有提前配置依赖管理,我们只需要指定groupId、artifactId即可,version就会使用spring boot中的默认版本,当然也可以强制指定版本)
com.fasterxml.jackson.jaxrs
jackson-jaxrs-xml-provider
[2.5.0,)
由于项目中同时添加JSON及XML的JAR包,按照spring MVC的默认响应处理流程是:如果未指定produces,则当请求的header中指定了accept类型,则自动格式化并返回该accept所需的类型,如果未指定accept类型,则优先是响应XML,当找不到XML依赖包时才会响应JSON,故如果项目中同时有JSON及XML,那么最好显式指定produces或者请求头上指明accept类型 这一点与ASP.NET WEB API原理相同,因为都是符合REST架构风格的。
效果如下:
二、使用Mybatis框架完成Domain层、DAO层(这里是Mapper层) ---提示:由于篇幅有限,只贴出重点能体现不同知识点的代码,其余可以到GITHUB上查看下载源码进行详细了解
2.0:首先在application.properties配置mybatis的相关选项,如下所示:
mybatis.type-aliases-package=cn.zuowenjun.boot.domain #包类型别名,这样在XML中就可以简写成类名
mybatis.config-location=classpath:mybatis/mybatis-config.xml #指定mybatis的配置文件路径
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml #指定mapper XML的存放路径
#这里是使用SQL SERVER,如果是其它DB则使用其它驱动
spring.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
spring.datasource.url=jdbc:sqlserver://DBIP:Port;DatabaseName=testDB
spring.datasource.username=dbuser
spring.datasource.password=dbpassword
其次添加mybatis-spring-boot-starter maven依赖,它会自动添加相关的mybatis依赖包,配置如下:
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.2
2.1全手写JAVA代码实现Mybatis的CRUD;
2.1.1.在cn.zuowenjun.boot.domain包【实体模型或称领域模型层,这里算不上真正的领域模型,最多算是贫血的领域模型】中定义数据实体模型(Goods:商品信息),代码如下:
packagecn.zuowenjun.boot.domain;importjava.math.BigDecimal;importjava.util.Date;public classGoods {private intid;privateString title;privateString picture;privateBigDecimal price;privateString introduction;private intcategoryId;privateString lastEditBy;privateDate lastEditTime;publicGoods() {
}public Goods(intid,String title,String picture,
BigDecimal price,String introduction,intcategoryId,String lastEditBy,Date lastEditTime) {this.setId(id);this.setTitle(title);this.setPicture(picture);this.setPrice(price);this.setIntroduction(introduction);this.setCategoryId(categoryId);this.setLastEditBy(lastEditBy);this.setLastEditTime(lastEditTime);
}public intgetId() {returnid;
}public void setId(intid) {this.id =id;
}publicString getTitle() {returntitle;
}public voidsetTitle(String title) {this.title =title;
}publicString getPicture() {returnpicture;
}public voidsetPicture(String picture) {this.picture =picture;
}publicBigDecimal getPrice() {returnprice;
}public voidsetPrice(BigDecimal price) {this.price =price;
}publicString getIntroduction() {returnintroduction;
}public voidsetIntroduction(String introduction) {this.introduction =introduction;
}public intgetCategoryId() {returncategoryId;
}public void setCategoryId(intcategoryId) {this.categoryId =categoryId;
}publicString getLastEditBy() {returnlastEditBy;
}public voidsetLastEditBy(String lastEditBy) {this.lastEditBy =lastEditBy;
}publicDate getLastEditTime() {returnlastEditTime;
}public voidsetLastEditTime(Date lastEditTime) {this.lastEditTime =lastEditTime;
}
}
View Code
2.1.2.在cn.zuowenjun.boot.mapper包【数据映射处理层或称DAO层】中定义数据映射处理接口及添加相应的SQL注解,以实现对数据进行CRUD,代码如下:
packagecn.zuowenjun.boot.mapper;import java.util.*;import org.apache.ibatis.annotations.*;import cn.zuowenjun.boot.domain.*;public interfaceGoodsMapper {
@Select("select * from TA_TestGoods order by id offset (${pageNo}-1)*${pageSize} rows fetch next ${pageSize} rows only")
List getListByPage(int pageSize,intpageNo);
@Select("select * from TA_TestGoods where categoryId=#{categoryId} order by id")
List getList(intcategoryId);
@Select("
+"#{item}"
+"order by id")
List getListByMultIds(@Param("ids")int...ids);
@Select("select * from TA_TestGoods where id=#{id}")
Goods get(intid);
@Insert(value="insert into TA_TestGoods(title, picture, price, introduction, categoryId, "
+ "lastEditBy, lastEditTime) values(#{title},#{picture},#{price},#{introduction},#{categoryId},#{lastEditBy},getdate())")
@Options(useGeneratedKeys=true,keyProperty="id",keyColumn="id")voidinsert(Goods goods);
@Delete(value="delete from TA_TestGoods where id=#{id}")void delete(intid);
@Update("update TA_TestGoods set title=#{title},picture=#{picture},price=#{price},introduction=#{introduction}," +
"categoryId=#{categoryId},lastEditBy=#{lastEditBy},lastEditTime=getdate() " +
"where id=#{id}")voidupdate(Goods goods);
}
如上代码重点说明:
a.增删改查,对应的注解是:insert、delete、update、select;
b.SQL注解中的参数占位符有两种,一种是:#{xxx},最后会生成?的参数化执行,另一种是:${xxx} 则最后会直接替换成参数的值,即拼SQL(除非信任参数或一些时间、数字类型,否则不建议这种,存在SQL注入风险);
c.insert时如果有自增ID,则可以通过添加Options注解,并指定useGeneratedKeys=true,keyProperty="数据实体类的属性字段名",keyColumn="表自增ID的字段名",这样当insert成功后会自动回填到数据实体类的自增ID对应的属性上;
d.如果想要生成in子句查询,则如上代码getListByMultIds方法上的select注解中使用格式实现,如果想用实现复杂的一对一,一对多,多对多等复杂的查询,则需要添加results注解并指定相应的关联关系,同时select SQL语句也应关联查询,可参见:https://blog.csdn.net/desert568/article/details/79079151
以上2步即完成一个mapper操作类;
2.2全手写AVA代码+Mapper XML实现Mybatis的CRUD;
2.2.1.仍然是在cn.zuowenjun.boot.domain包中定义一个数据实体模型类(ShoppingCart:购物车信息),代码如下:【注意这里有一个关联商品信息的属性:inGoods】
packagecn.zuowenjun.boot.domain;importjava.util.Date;public classShoppingCart {private intid;privateString shopper;private intgoodsId;private intqty;privateDate addedTime;privateGoods inGoods;publicShoppingCart() {
}public ShoppingCart(int id,String shopper,int goodsId,intqty,Date addedTime) {this.id=id;this.shopper=shopper;this.goodsId=goodsId;this.qty=qty;this.addedTime=addedTime;
}public intgetId() {returnid;
}public void setId(intid) {this.id =id;
}publicString getShopper() {returnshopper;
}public voidsetShopper(String shopper) {this.shopper =shopper;
}public intgetGoodsId() {returngoodsId;
}public void setGoodsId(intgoodsId) {this.goodsId =goodsId;
}public intgetQty() {returnqty;
}public void setQty(intqty) {this.qty =qty;
}publicDate getAddedTime() {returnaddedTime;
}public voidsetAddedTime(Date addedTime) {this.addedTime =addedTime;
}publicGoods getInGoods() {returninGoods;
}public voidsetInGoods(Goods inGoods) {this.inGoods =inGoods;
}
}
View Code
2.2.2.仍然是在cn.zuowenjun.boot.mapper包中定义数据操作接口(interface),注意这里只是定义接口,并不包含SQL注解部份,因为这部份将在Mapper的XML代码中进行配置实现,代码如下:
packagecn.zuowenjun.boot.mapper;importjava.util.List;importorg.apache.ibatis.annotations.Param;import cn.zuowenjun.boot.domain.*;public interfaceShoppingCartMapper {
ListgetList(String shopper);voidinsert(ShoppingCart shoppingCart);voidupdate(ShoppingCart shoppingCart);void deleteItem(intid);voiddelete(String shopper);intgetBuyCount(String shopper);
ShoppingCart get(@Param("shopper") String shopper,@Param("goodsId") intgoodsId);
}
如上代码有一个重点说明:get方法有两个参数(多个参数也类似),为了mybatis能够自动映射到这些参数,必需为每个参数添加Param注解,并指定参数名,这个参数名是与对应的Mapper XML中的SQL语句中定义的参数名相同。
2.2.3.在mybatis.mapper-locations设置的mapper xml存放的路径中创建XML文件,并手动编写映射的SQL语句,如下所示:
select * from TA_TestShoppingCart a inner join TA_TestGoods b on a.goodsId=b.id
where shopper=#{shopper} order by addedTime
select count(1) from (select goodsId from TA_TestShoppingCart where shopper=#{shopper}
group by goodsId) as t
select * from TA_TestShoppingCart a inner join TA_TestGoods b on a.goodsId=b.id
where shopper=#{shopper} and goodsId=#{goodsId}
insert into TA_TestShoppingCart(shopper, goodsId, qty, addedTime)
values(#{shopper},#{goodsId},#{qty},getdate())
update TA_TestShoppingCart set shopper=#{shopper},goodsId=#{goodsId},qty=#{qty},addedTime=getdate()
where id=#{id}
delete from TA_TestShoppingCart where id=#{id}
delete from TA_TestShoppingCart where shopper=#{shopper}
如上XML重点说明:
a.凡是使用到类型的地方,可以在mybatis-config.xml中提前配置类型别名,以简化配置,当然mybatis已默认设置了一些别名以减少大家配置的工作量,如:string,对应的类型是String等,详见:http://www.mybatis.org/mybatis-3/zh/configuration.html#typeAliases
b.由于这个ShoppingCart有关联属性:inGoods,故在查询时都会关联查询goods表并通过在resultMap中通过association 元素来指定关联关系,更多复杂的XML配置详见:http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html
以上3步即完成一个mapper操作类,相比直接使用mapper接口+SQL注解多了一个步骤,但这样的好处是由于没有写死在代码中,可以很容易的更改mapper的相关SQL语句,减少代码改动量。
2.3使用Mybatis Generator的Maven插件自动生成Mybatis的CRUD;
通过上面的介绍,我们知道有2种方法来实现一个mapper数据操作类(dao),显然第2种更能适应更改的情况,但由于手写mapper xml文件非常的麻烦,故可以通过Mybatis Generator组件,自动生成相关的代码及xml(一般是:数据实体类domain、数据处理接口mapper、mapper XML),具体实现步骤如下:(可以单独一个项目来生成这些文件,也可以集成在一个项目中,由于是演示,我这里是集成在一个项目中)
2.3.1.由于要使用Mybatis Generator组件,故需要添加对应的JAR包依赖,如下所示:
com.microsoft.sqlserver
mssql-jdbc
7.0.0.jre8
org.mybatis.generator
mybatis-generator-core
1.3.7
同时需要添加对应的maven插件,以便通过maven命令可执行生成过程,如下:(通过configurationFile元素指定生成器的配置路径,overwrite元素指定是否覆盖生成,这里有个坑,后面会介绍到,此处略)
org.mybatis.generator
mybatis-generator-maven-plugin
1.3.7
src/main/resources/mybatis/generatorconfig.xml
true
true
2.3.2.在cn.zuowenjun.boot.domain包中定义相关的数据实体模型类,我这里演示的类是:ShoppingOrder(购物订单信息),代码如下:
packagecn.zuowenjun.boot.domain;importjava.math.BigDecimal;importjava.util.Date;public classShoppingOrder {privateInteger id;privateString shopper;privateInteger totalqty;privateBigDecimal totalprice;privateBoolean iscompleted;privateString createby;privateDate createtime;publicInteger getId() {returnid;
}public voidsetId(Integer id) {this.id =id;
}publicString getShopper() {returnshopper;
}public voidsetShopper(String shopper) {this.shopper = shopper == null ? null: shopper.trim();
}publicInteger getTotalqty() {returntotalqty;
}public voidsetTotalqty(Integer totalqty) {this.totalqty =totalqty;
}publicBigDecimal getTotalprice() {returntotalprice;
}public voidsetTotalprice(BigDecimal totalprice) {this.totalprice =totalprice;
}publicBoolean getIscompleted() {returniscompleted;
}public voidsetIscompleted(Boolean iscompleted) {this.iscompleted =iscompleted;
}publicString getCreateby() {returncreateby;
}public voidsetCreateby(String createby) {this.createby = createby == null ? null: createby.trim();
}publicDate getCreatetime() {returncreatetime;
}public voidsetCreatetime(Date createtime) {this.createtime =createtime;
}
}
View Code
/p>
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
View Code
由于涉及的知识点比较多,在此就不作介绍,请参见我给出的链接加以了解。
2.3.4.通过maven 插件来执行生成代码(生成代码有很多种方法,详见:https://blog.csdn.net/qq_32786873/article/details/78226925),这里我使用最为方便的一种,步骤如下:
项目右键-》RunAs或者DeBug-》Maven Build...-》在goals(阶段)中输入:mybatis-generator:generate,即:设置生成阶段,最后点击Apply或直接Run即可,如图示:
执行生成后,会在控制台中显示最终的结果,如下图示:如果成功会显示buid success,并会在相应的目录中生成对应的文件
2.4进阶用法:自定义Mybatis Generator的生成过程中的插件类,以便添加额外自定义的方法
虽然使用Mybatis Generator减少了手工编写代码及XML的工作量,但由于生成的CRUD方法都是比较简单的,稍微复杂或灵活一点的方法都不能简单生成,如果单纯的在生成代码后再人工手动添加其它自定义的方法,又担心如果执行一次自动生成又会覆盖手动添加的自定义代码,那有没有办法解决呢?当然是有的,我(梦在旅途,zuowenjun.cn)在网络上了解到的方法大部份都是说获取Mybatis Generator源代码,然后进行二次开发,最后使用“定制版”的Mybatis Generator,我个人觉得虽然能解决问题,但如果能力不足,可能会出现意想不到的问题,而且进行定制也不是那么简单的,故我这里采取Mybatis Generator框架提供的可扩展插件plugin来实现扩展,具体步骤如下:
2.4.1.在项目新创建一个包cn.zuowenjun.boot.mybatis.plugin,然后在包里面先创建一个泛型通用插件基类(CustomAppendMethodPlugin),这个基类主要是用于附加自定义方法,故取名CustomAppendMethodPlugin,代码如下:
packagecn.zuowenjun.boot.mybatis.plugin;importjava.util.List;importorg.mybatis.generator.api.IntrospectedTable;importorg.mybatis.generator.api.PluginAdapter;importorg.mybatis.generator.api.dom.java.Interface;importorg.mybatis.generator.api.dom.java.TopLevelClass;importorg.mybatis.generator.api.dom.xml.Document;importorg.mybatis.generator.codegen.mybatis3.javamapper.elements.AbstractJavaMapperMethodGenerator;importorg.mybatis.generator.codegen.mybatis3.xmlmapper.elements.AbstractXmlElementGenerator;/** 自定义通用可添加生成自定义方法插件类
* Author:zuowenjun
* Date:2019-1-29*/
public abstract class CustomAppendMethodPlugin
extendsPluginAdapter {protected final ClassteClass;protected final ClasstmClass;
@SuppressWarnings("unchecked")public CustomAppendMethodPlugin(Class extends AbstractXmlElementGenerator>teClass,
Class extends AbstractJavaMapperMethodGenerator>tmClass) {this.teClass=(Class) teClass;this.tmClass=(Class) tmClass;
}
@Overridepublic booleansqlMapDocumentGenerated(Document document,
IntrospectedTable introspectedTable) {try{
AbstractXmlElementGenerator elementGenerator=teClass.newInstance();
elementGenerator.setContext(context);
elementGenerator.setIntrospectedTable(introspectedTable);
elementGenerator.addElements(document.getRootElement());
}catch (InstantiationException |IllegalAccessException e) {//TODO Auto-generated catch block
e.printStackTrace();
}return super.sqlMapDocumentGenerated(document, introspectedTable);
}
@Overridepublic booleanclientGenerated(Interface interfaze,
TopLevelClass topLevelClass,
IntrospectedTable introspectedTable) {try{
AbstractJavaMapperMethodGenerator methodGenerator=tmClass.newInstance();
methodGenerator.setContext(context);
methodGenerator.setIntrospectedTable(introspectedTable);
methodGenerator.addInterfaceElements(interfaze);
}catch (InstantiationException |IllegalAccessException e) {//TODO Auto-generated catch block
e.printStackTrace();
}return super.clientGenerated(interfaze, topLevelClass, introspectedTable);
}
@Overridepublic boolean validate(Listwarnings) {//TODO Auto-generated method stub
return true;
}
}
View Code
代码比较简单,主要是重写了sqlMapDocumentGenerated(生成mapper xml方法)、clientGenerated(生成mapper 接口方法),在这里面我通过把指定泛型类型(分别继承自 AbstractXmlElementGenerator、AbstractJavaMapperMethodGenerator)加入到生成XML和接口的过程中,以实现生成过程的抽象。
2.4.2.我这里由于默认生成的ShoppingOrderDetailMapper(实体类:ShoppingOrderDetail是购物订单详情)无法满足需要,我需要额外再增加两个方法:
List selectByOrderId(int shoppingOrderId); 、void deleteByOrderId(int shoppingOrderId); 故在这里自定义继承自CustomAppendMethodPlugin的插件类:ShoppingOrderDetailMapperPlugin,具体实现代码如下:
packagecn.zuowenjun.boot.mybatis.plugin;importjava.util.Set;importjava.util.TreeSet;importorg.mybatis.generator.api.dom.java.FullyQualifiedJavaType;importorg.mybatis.generator.api.dom.java.Interface;importorg.mybatis.generator.api.dom.java.JavaVisibility;importorg.mybatis.generator.api.dom.java.Method;importorg.mybatis.generator.api.dom.java.Parameter;importorg.mybatis.generator.api.dom.xml.Attribute;importorg.mybatis.generator.api.dom.xml.TextElement;importorg.mybatis.generator.api.dom.xml.XmlElement;importorg.mybatis.generator.codegen.mybatis3.javamapper.elements.AbstractJavaMapperMethodGenerator;importorg.mybatis.generator.codegen.mybatis3.xmlmapper.elements.AbstractXmlElementGenerator;/** ref seehttps://www.cnblogs.com/se7end/p/9293755.html* Author:zuowenjun
* Date:2019-1-29*/
public classShoppingOrderDetailMapperPluginextends CustomAppendMethodPlugin{publicShoppingOrderDetailMapperPlugin() {super(ShoppingOrderDetailXmlElementGenerator.class,ShoppingOrderDetailJavaMapperMethodGenerator.class);
}
}class ShoppingOrderDetailXmlElementGenerator extendsAbstractXmlElementGenerator{
@Overridepublic voidaddElements(XmlElement parentElement) {if(!introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime().equalsIgnoreCase("TA_TestShoppingOrderDetail")) {return;
}
TextElement selectText= new TextElement("select * from " +introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime()+ " where shoppingOrderId=#{shoppingOrderId}");
XmlElement selectByOrderId= new XmlElement("select");
selectByOrderId.addAttribute(new Attribute("id", "selectByOrderId"));
selectByOrderId.addAttribute(new Attribute("resultMap", "BaseResultMap"));
selectByOrderId.addAttribute(new Attribute("parameterType", "int"));
selectByOrderId.addElement(selectText);
parentElement.addElement(selectByOrderId);
TextElement deleteText= new TextElement("delete from " +introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime()+ " where shoppingOrderId=#{shoppingOrderId}");
XmlElement deleteByOrderId= new XmlElement("delete");
deleteByOrderId.addAttribute(new Attribute("id", "deleteByOrderId"));
deleteByOrderId.addAttribute(new Attribute("parameterType", "int"));
deleteByOrderId.addElement(deleteText);
parentElement.addElement(deleteByOrderId);
}
}class ShoppingOrderDetailJavaMapperMethodGenerator extendsAbstractJavaMapperMethodGenerator{
@Overridepublic voidaddInterfaceElements(Interface interfaze) {if(!introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime().equalsIgnoreCase("TA_TestShoppingOrderDetail")) {return;
}
addInterfaceSelectByOrderId(interfaze);
addInterfaceDeleteByOrderId(interfaze);
}private voidaddInterfaceSelectByOrderId(Interface interfaze) {//先创建import对象
Set importedTypes = new TreeSet();//添加Lsit的包
importedTypes.add(FullyQualifiedJavaType.getNewListInstance());//创建方法对象
Method method = newMethod();//设置该方法为public
method.setVisibility(JavaVisibility.PUBLIC);//设置返回类型是List
FullyQualifiedJavaType returnType =FullyQualifiedJavaType.getNewListInstance();
FullyQualifiedJavaType listArgType= newFullyQualifiedJavaType(introspectedTable.getBaseRecordType());
returnType.addTypeArgument(listArgType);//方法对象设置返回类型对象
method.setReturnType(returnType);//设置方法名称为我们在IntrospectedTable类中初始化的 “selectByOrderId”
method.setName("selectByOrderId");//设置参数类型是int类型
FullyQualifiedJavaType parameterType;
parameterType=FullyQualifiedJavaType.getIntInstance();//import参数类型对象(基本类型其实可以不必引入包名)//importedTypes.add(parameterType);//为方法添加参数,变量名称record
method.addParameter(new Parameter(parameterType, "shoppingOrderId")); //$NON-NLS-1$//context.getCommentGenerator().addGeneralMethodComment(method, introspectedTable);if(context.getPlugins().clientSelectByPrimaryKeyMethodGenerated(method, interfaze, introspectedTable)) {
interfaze.addImportedTypes(importedTypes);
interfaze.addMethod(method);
}
}private voidaddInterfaceDeleteByOrderId(Interface interfaze) {//创建方法对象
Method method = newMethod();//设置该方法为public
method.setVisibility(JavaVisibility.PUBLIC);//设置方法名称为我们在IntrospectedTable类中初始化的 “deleteByOrderId”
method.setName("deleteByOrderId");//设置参数类型是int类型
FullyQualifiedJavaType parameterType;
parameterType=FullyQualifiedJavaType.getIntInstance();
method.addParameter(new Parameter(parameterType, "shoppingOrderId")); //$NON-NLS-1$
context.getCommentGenerator().addGeneralMethodComment(method, introspectedTable);if(context.getPlugins().clientSelectByPrimaryKeyMethodGenerated(method, interfaze, introspectedTable)) {
interfaze.addMethod(method);
}
}
}
从如上代码所示,核心点是自定义继承自AbstractXmlElementGenerator、AbstractJavaMapperMethodGenerator的ShoppingOrderDetailXmlElementGenerator(XML生成器类)、ShoppingOrderDetailJavaMapperMethodGenerator(mapper接口生成器类),然后分别在addElements、addInterfaceElements添加自定义生成XML及接口方法的逻辑(如上代码中使用的是反射,若想学习了解反射请自行网上查找相关资料,C#也有反射哦,应该好理解),注意由于插件在生成过程中每个实体类都会调用一次,故必需作相应的判断(判断当前要附加的自定义方法是符与当前实体类生成过程相符,如果不相符则忽略退出)
如下是ShoppingOrderDetail实体类的代码:
packagecn.zuowenjun.boot.domain;importjava.math.BigDecimal;importjava.util.Date;public classShoppingOrderDetail {privateInteger id;privateInteger shoppingorderid;privateInteger goodsid;privateInteger qty;privateBigDecimal totalprice;privateString createby;privateDate createtime;publicInteger getId() {returnid;
}public voidsetId(Integer id) {this.id =id;
}publicInteger getShoppingorderid() {returnshoppingorderid;
}public voidsetShoppingorderid(Integer shoppingorderid) {this.shoppingorderid =shoppingorderid;
}publicInteger getGoodsid() {returngoodsid;
}public voidsetGoodsid(Integer goodsid) {this.goodsid =goodsid;
}publicInteger getQty() {returnqty;
}public voidsetQty(Integer qty) {this.qty =qty;
}publicBigDecimal getTotalprice() {returntotalprice;
}public voidsetTotalprice(BigDecimal totalprice) {this.totalprice =totalprice;
}publicString getCreateby() {returncreateby;
}public voidsetCreateby(String createby) {this.createby = createby == null ? null: createby.trim();
}publicDate getCreatetime() {returncreatetime;
}public voidsetCreatetime(Date createtime) {this.createtime =createtime;
}
}
View Code
另外顺便解决一个踩坑点:上面提到了,我们在POM文件配置mybatis-generator-maven-plugin插件时,overwrite设为true,目的是确保每次执行生成时,生成的代码能够覆盖已经存在的,理想是美好的,但现实总会有点小意外,我们这样配置,只能解决生成的mapper 接口类文件不会重复,但生成的mapper xml文件仍然会附加代码导致重复,故我们需要解决这个问题,而解决这个问题的关键是:GeneratedXmlFile.isMergeable,如果isMergeable为true则会合并,目前默认都是false,所以我们只需实现将GeneratedXmlFile.isMergeable设为true即可,由于isMergeable是私有字段,只能采取插件+反射动态改变这个值了,自定义合并代码插件OverIsMergeablePlugin实现如下:
packagecn.zuowenjun.boot.mybatis.plugin;importjava.lang.reflect.Field;importjava.util.List;importorg.mybatis.generator.api.GeneratedXmlFile;importorg.mybatis.generator.api.IntrospectedTable;importorg.mybatis.generator.api.PluginAdapter;/** 修复mybatis-generator重复执行时生成的XML有重复代码(核心:isMergeable=false)
* Author:https://blog.csdn.net/zengqiang1/article/details/79381418* Editor:zuowenjun*/
public class OverIsMergeablePlugin extendsPluginAdapter {
@Overridepublic boolean validate(Listwarnings) {return true;
}
@Overridepublic booleansqlMapGenerated(GeneratedXmlFile sqlMap,
IntrospectedTable introspectedTable) {try{
Field field= sqlMap.getClass().getDeclaredField("isMergeable");
field.setAccessible(true);
field.setBoolean(sqlMap,false);
}catch(Exception e) {
e.printStackTrace();
}return true;
}
}
2.4.3.在generatorconfig.xml配置文件中增加plugin配置,如下:
... ...省略中间过程
2.4.4.由于不能在同一个项目中直接使用plugin类(具体原因请上网查询,在此了解即可),故还需把cn.zuowenjun.boot.mybatis.plugin这个包中的文件单独导出生成JAR包,然后把这个JAR包复制到项目的指定目录下(本示例是放在libs目录下),然后再在POM为mybatis-generator-maven-plugin单独添加system本地依赖才行,maven添加依赖如下:
org.mybatis.generator
mybatis-generator-maven-plugin
1.3.7
src/main/resources/mybatis/generatorconfig.xml
true
true
cn.zuowenjun.boot.mybatis.plugin
cn.zuowenjun.boot.mybatis.plugin
1.0
system
${basedir}/src/main/libs/cn.zuowenjun.boot.mybatis.plugin.jar
如果4步完成后,最后执行maven buid的生成mybatis代码过程即可,最后查看生成的mapper及xml都会有对应的自定义方法,在此就不再贴出结果了。
2.5进阶用法:利用Mybatis的继承机制实现添加额外自定义方法
如2.4节所述,我们可以通过自定义plugin来实现添加额外自定义的方法,而且不用担心被覆盖,但可能实现有点麻烦(里面用到了反射),有没有简单一点的办法呢?当然有,即可以先使用Mybatis Generator框架生成默代代码,然后再结合使用2.2所述方法(手写mapper接口类及mapper XML),利用mapper XML的继承特性完成添加自定义方法的过程中,具体步骤与2.2相同,在此贴出(注意前提是先自动生成代码,然后再操作如下步骤)
2.5.1.定义扩展mapper接口类(ShoppingOrderExtMapper,扩展ShoppingOrderMapper,它们之间无需继承),代码如下:(很简单,就是定义了一个特殊用途的方法)
packagecn.zuowenjun.boot.mapper;importjava.util.List;importcn.zuowenjun.boot.domain.ShoppingOrder;public interfaceShoppingOrderExtMapper {
ListselectAllByShopper(String shopper);
}
2.5.2.编写对应的ShoppingOrderExtMapper.xml,这里面就要用到继承,继承主要是resultMap【实现继承用:extends=要继承的mapper xml resultMap】,这样就不用两个地方都为一个实体类写结果映射配置了,其余的都按一个新的mapper XML配置来设计即可,代码如下:
select * from TA_TestShoppingOrder where shopper=#{shopper}
如上两步即完成扩展添加额外自定义的方法,又不用担心重复执行生成代码会被覆盖掉,只是使用时需要单独注册到spring,单独实例,虽不完美但弥补了默认生成代码的不足也是可行的。
2.6 使用SpringBootTest + junit测试基于Mybatis框架实现的DAO类
在此不详情说明junit测试的用法,网上大把资源,只是单独说明结合SpringBootTest 注解,完成单元测试,先看单元测试代码:
packagecn.zuowenjun.springbootdemo;importjava.math.BigDecimal;importjava.util.Date;importjava.util.List;importorg.junit.Assert;importorg.junit.Test;importorg.junit.runner.RunWith;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.boot.test.context.SpringBootTest;importorg.springframework.test.annotation.Rollback;importorg.springframework.test.context.junit4.SpringRunner;importorg.springframework.transaction.annotation.Transactional;importcn.zuowenjun.boot.SpringbootdemoApplication;import cn.zuowenjun.boot.domain.*;importcn.zuowenjun.boot.mapper.GoodsMapper;importcn.zuowenjun.boot.mapper.ShoppingOrderDetailMapper;importcn.zuowenjun.boot.mapper.ShoppingOrderMapper;
@RunWith(SpringRunner.class)
@SpringBootTest(classes=SpringbootdemoApplication.class)public classShoppingOrderMapperTests {
@AutowiredprivateShoppingOrderMapper shoppingOrderMapper;
@AutowiredprivateShoppingOrderDetailMapper shoppingOrderDetailMapper;
@AutowiredprivateGoodsMapper goodsMapper;
@Transactional
@Rollback(false) //不加这个,默认测试完后自动回滚
@Testpublic voidtestInsertShoppingOrder() {
Goods goods= goodsMapper.get(1);
ShoppingOrder shoppingOrder=newShoppingOrder();
shoppingOrder.setShopper("zuowenjun");
shoppingOrder.setIscompleted(false);
shoppingOrder.setTotalprice(BigDecimal.valueOf(0));
shoppingOrder.setTotalqty(1);
shoppingOrder.setCreateby("zuowenjun");
shoppingOrder.setCreatetime(newDate());int orderId=shoppingOrderMapper.insert(shoppingOrder);
shoppingOrder.setId(orderId);
ShoppingOrderDetail shoppingOrderDetail=newShoppingOrderDetail();
shoppingOrderDetail.setGoodsid(goods.getId());
shoppingOrderDetail.setShoppingorderid(shoppingOrder.getId());
shoppingOrderDetail.setQty(10);
shoppingOrderDetail.setTotalprice(BigDecimal.valueOf(shoppingOrderDetail.getQty()).multiply(goods.getPrice()));
shoppingOrderDetail.setCreateby("zuowenjun");
shoppingOrderDetail.setCreatetime(newDate());
shoppingOrderDetailMapper.insert(shoppingOrderDetail);
List orderDetails=shoppingOrderDetailMapper.selectByOrderId(shoppingOrder.getId());if(orderDetails!=null && orderDetails.size()>0) {for(ShoppingOrderDetail od:orderDetails) {
System.out.println("id:" + od.getId() + ",goodsid:" +od.getGoodsid());
}
}
Assert.assertTrue(orderDetails.size()>0);
}
}
View Code
与Junit单元测试用法基本相同,唯 一的区别就是在单元测试的类上添加@SpringBootTest,并指定启动类(如代码中所示:@SpringBootTest(classes=SpringbootdemoApplication.class)),另外注意一点:如果测试方法使用@Transactional注解,那么当测试完成后会回滚(即并不会提交事务),如果想完成事务的提交,则需如代码中所示添加@Rollback(false),其中false指不回滚,true则为回滚。
三、简单演示集成Thymeleaf模板引擎(这里只是用一个简单的页面演示效果,由于现在都流行前后端分离,故只需了解即可)
说明:Thymeleaf是spring MVC的端视图引擎,与JSP视图引擎类似,只不过在spring boot项目中默认支持Thymeleaf(Thymeleaf最大的优点是视图中不含JAVA代码,不影响UI美工及前端设计),而JSP不建议使用,当然也可以通过添加相关的JSP的JAR包依赖,实现JSP视图,具体请自行网上查找资源,同时spring MVC +JSP视图的用法可以参见该系列的上篇文章
3.1.添加Thymeleaf的maven依赖,POM配置如下:
org.springframework.boot
spring-boot-starter-thymeleaf
3.2.编写后端controller,以便响应用户请求,代码如下:(这个与普通spring MVC+JSP相同,区别在VIEW)
packagecn.zuowenjun.boot.controller;importjava.util.List;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Controller;importorg.springframework.ui.Model;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RequestMapping;import cn.zuowenjun.boot.domain.*;import cn.zuowenjun.boot.service.*;
@Controller
@RequestMapping("/test")public classTestController {
@AutowiredprivateShopUserService shopUserService;
@GetMapping("/userlist")publicString list(Model model) {
List users=shopUserService.getAll();
model.addAttribute("title", "测试使用thymeleaf模板引擎展示数据");
model.addAttribute("users", users);//可以在application.properties添加如下配置,以改变thymeleaf的默认设置//spring.thymeleaf.prefix="classpath:/templates/" 模板查找路径//spring.thymeleaf.suffix=".html" 模板后缀名
return "/test";//默认自动查找路径:src/main/resources/templates/*.html
}
}
3.3编写前端视图html模板页面,最后演示效果
HTML视图页面代码:(th:XXX为Thymeleaf的模板特有的标识符,${xxx}这是SP EL表达式,这个之前讲过的,很简单,不展开说明)
test User List -power by thymeleaftable{border:2px solid blue;border-collapse:collapse;width:98%;
}table *{border:1px solid blue;text-align:center;
}thead{background-color:purple;color:yellow;
}th,td{padding:5px;
}#copy{margin-top:100px;text-align:center;
}
SeqNouserIdnickNamedepositAmount
1
www.zuowenjun.cn
梦在旅途
520
暂无相关记录!
Copyright©www.zuowenjun.cn and zuowj.cnblogs.com demo all rights.