java spring写一个网站_JAVA WEB快速入门之从编写一个基于SpringBoot+Mybatis快速创建的REST API项目了解SpringBoot、SpringMVC REST AP...

本文介绍了如何基于Spring Boot+Mybatis快速构建REST API项目,包括创建Spring Boot项目、解决常见问题、实现REST API的JSON和XML响应、集成Mybatis进行CRUD操作。此外,还涉及到Mybatis代码生成器的使用、自定义插件解决重复代码问题以及SpringBootTest单元测试。最后简单演示了集成Thymeleaf模板引擎。
摘要由CSDN通过智能技术生成

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空项目,如下图示:

6bff9cdeff19f4c2302bcfecd313c19a.png

设置后点击页面的生成项目按钮,即可生成并下载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

设置后项目的视图就有如下显示效果:

e6d0c3f6a4f5d453326ffd7e3e00c70d.png

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架构风格的。

效果如下:

bedd7763760c869e557c4e688e96aaf0.png     

7c09dfd6adc7cc3a24f50d2da23ea6d9.png

二、使用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:商品信息),代码如下:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

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】

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

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(购物订单信息),代码如下:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

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

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

/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即可,如图示:

78453842b2d6e9f086eb93b0ca5689f5.png

执行生成后,会在控制台中显示最终的结果,如下图示:如果成功会显示buid success,并会在相应的目录中生成对应的文件

882d0934a61cfd4aacaf1667ea1b8203.png

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,代码如下:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

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实体类的代码:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

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 注解,完成单元测试,先看单元测试代码:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

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表达式,这个之前讲过的,很简单,不展开说明)

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

test User List -power by thymeleaf

table{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.

去几年,REST逐渐成为影响Web框架、Web协议与Web应用设计的重要概念。如果你还不了解REST,那这个简短的介绍将有助你快速掌握REST,此外还可以点击这里了解关于REST的更多信息。 相关厂商内容 高速下载:Adobe Flash Builder 4 简体中文正式版 for Windows 高速下载:Adobe Flash Builder 4 简体中文正式版 for Mac 利用Flex SDK创建易于访问的Adobe AIR应用程序 Adobe和英特尔联手推新服务帮助开发者发行AIR应用 构建更加完善的Adobe AIR应用程序之十大秘诀 相关赞助商 汇集最新RIA技术相关资源,提供Flash开发平台相关工具高速下载,免费获得Adobe软件的产品序列号。 现在有越来越多的公司希望能以简单而又贴合Web架构本身的方式公开Web API,因此REST变得越来越重要也就不足为奇了。使用Ajax进行通信的富浏览器端也在朝这个目标不断迈进。这个架构原则提升了万维网的可伸缩性,无论何种应用都能从该原则中受益无穷。 JAX-RS(JSR 311)指的是Java API for RESTful Web Services,Roy Fielding也参与了JAX-RS的制订,他在自己的博士论文中定义了REST。对于那些想要构建RESTful Web Services的开发者来说,JAX-RS给出了不同于JAX-WS(JSR-224)的另一种解决方案。目前共有4种JAX-RS实现,所有这些实现都支持Spring,Jersey则是JAX-RS的参考实现,也是本文所用的实现。 如果你使用Spring进行开发,那可能想知道(或者有人曾问过你)Spring MVC与JAX-RS有何异同点?更进一步,如果你手头有一个Spring MVC应用,使用了控制类继承(SimpleFormController等),你可能还意识不到现在的Spring MVC对REST广泛的支持。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值