JAVA WEB快速入门之从编写一个基于SpringBoot+Mybatis快速创建的REST API项目了解SpringBoot、SpringMVC REST API、Mybatis等相关知识
java web快速入门系列之前的相关文章如下:(文章全部本人【梦在旅途原创】,文中内容可能部份图片、代码参照网上资源)
第二篇:java web快速入门之从编写一个jsp web网站了解jsp web网站的基本结构、调试、部署
第三篇:java web快速入门之通过一个简单的spring项目了解spring的核心(aop、ioc)
第四篇:java web快速入门之从编写一个基于springmvc框架的网站了解maven、springmvc、springjdbc
今天是第五篇,也是该系列文章的最后一篇,接上篇《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空项目,如下图示:
(当然也可以通过eclipse或idea的spring boot插件来创建,可参见:,)
设置后点击页面的生成项目按钮,即可生成并下载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 class springbootdemoapplication { public static void main(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依赖:
<dependency> <groupid>org.apache.maven.plugins</groupid> <artifactid>maven-resources-plugin</artifactid> <version>2.5</version> <type>maven-plugin</type> </dependency>
1.2.3.设置热编译启动模式,以便可以随时更改代码后即时生效
<dependencies> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-devtools</artifactid> <optional>true</optional> </dependency> </dependencies> <build> <plugins> <plugin> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-maven-plugin</artifactid> <configuration> <fork>true</fork> </configuration> </plugin> </plugins> </build>
设置后项目的视图就有如下显示效果:
1.3演示请求rest api分别返回json、xml
创建好spring boot空项目环境后,我们就可以开始编写相关代码了,在此仅贴出实现了rest api分别响应返回json、xml格式的controller,实现步骤如下:
1.3.1在cn.zuowenjun.boot.controller包中创建democontroller,并编写hellojson、helloxml action方法,代码如下:
package cn.zuowenjun.boot.controller; import org.springframework.web.bind.annotation.*; import cn.zuowenjun.boot.domain.*; @restcontroller public class democontroller { @requestmapping(value="/hello/json",produces="application/json;charset=utf-8") public hellodto hellojson() { hellodto dto=new hellodto(); dto.setmessage("hello,zuowenjun.cn,hello java spring boot!"); return dto; } @requestmapping(value="/hello/xml",produces="text/xml;charset=utf-8") public hellodto helloxml() { hellodto dto=new hellodto(); dto.setmessage("hello,zuowenjun.cn,hello java spring boot!"); return dto; } }
如上代码简要说明:@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中的默认版本,当然也可以强制指定版本)
<!-- 如果项目中需要rest api响应(返回)xml格式的报文体则应添加该依赖 --> <dependency> <groupid>com.fasterxml.jackson.jaxrs</groupid> <artifactid>jackson-jaxrs-xml-provider</artifactid> <version>[2.5.0,)</version><!--$no-mvn-man-ver$ --> </dependency>
由于项目中同时添加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依赖包,配置如下:
<!-- 添加 mybatis-spring-boot依赖,直接可以使用mybatis环境操作db--> <dependency> <groupid>org.mybatis.spring.boot</groupid> <artifactid>mybatis-spring-boot-starter</artifactid> <version>1.3.2</version> </dependency>
2.1全手写java代码实现mybatis的crud;
2.1.1.在cn.zuowenjun.boot.domain包【实体模型或称领域模型层,这里算不上真正的领域模型,最多算是贫血的领域模型】中定义数据实体模型(goods:商品信息),代码如下:
package cn.zuowenjun.boot.domain; import java.math.bigdecimal; import java.util.date; public class goods { private int id; private string title; private string picture; private bigdecimal price; private string introduction; private int categoryid; private string lasteditby; private date lastedittime; public goods() { } public goods(int id,string title,string picture, bigdecimal price,string introduction,int categoryid,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 int getid() { return id; } public void setid(int id) { this.id = id; } public string gettitle() { return title; } public void settitle(string title) { this.title = title; } public string getpicture() { return picture; } public void setpicture(string picture) { this.picture = picture; } public bigdecimal getprice() { return price; } public void setprice(bigdecimal price) { this.price = price; } public string getintroduction() { return introduction; } public void setintroduction(string introduction) { this.introduction = introduction; } public int getcategoryid() { return categoryid; } public void setcategoryid(int categoryid) { this.categoryid = categoryid; } public string getlasteditby() { return lasteditby; } public void setlasteditby(string lasteditby) { this.lasteditby = lasteditby; } public date getlastedittime() { return lastedittime; } public void setlastedittime(date lastedittime) { this.lastedittime = lastedittime; } }
2.1.2.在cn.zuowenjun.boot.mapper包【数据映射处理层或称dao层】中定义数据映射处理接口及添加相应的sql注解,以实现对数据进行crud,代码如下:
package cn.zuowenjun.boot.mapper; import java.util.*; import org.apache.ibatis.annotations.*; import cn.zuowenjun.boot.domain.*; public interface goodsmapper { @select("select * from ta_testgoods order by id offset (${pageno}-1)*${pagesize} rows fetch next ${pagesize} rows only") list<goods> getlistbypage(int pagesize,int pageno); @select("select * from ta_testgoods where categoryid=#{categoryid} order by id") list<goods> getlist(int categoryid); @select("<script>select * from ta_testgoods where id in " +"<foreach item='item' index='index' collection='ids' open='(' separator=',' close=')'>#{item}</foreach>" +"order by id</script>") list<goods> getlistbymultids(@param("ids")int...ids); @select("select * from ta_testgoods where id=#{id}") goods get(int id); @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") void insert(goods goods); @delete(value="delete from ta_testgoods where id=#{id}") void delete(int id); @update("update ta_testgoods set title=#{title},picture=#{picture},price=#{price},introduction=#{introduction}," + "categoryid=#{categoryid},lasteditby=#{lasteditby},lastedittime=getdate() " + "where id=#{id}") void update(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注解中使用<script>xxx<foreach>xx</foreach>xx</script>格式实现,如果想用实现复杂的一对一,一对多,多对多等复杂的查询,则需要添加results注解并指定相应的关联关系,同时select sql语句也应关联查询,可参见:
以上2步即完成一个mapper操作类;
2.2全手写ava代码+mapper xml实现mybatis的crud;
2.2.1.仍然是在cn.zuowenjun.boot.domain包中定义一个数据实体模型类(shoppingcart:购物车信息),代码如下:【注意这里有一个关联商品信息的属性:ingoods】
package cn.zuowenjun.boot.domain; import java.util.date; public class shoppingcart { private int id; private string shopper; private int goodsid; private int qty; private date addedtime; private goods ingoods; public shoppingcart() { } public shoppingcart(int id,string shopper,int goodsid,int qty,date addedtime) { this.id=id; this.shopper=shopper; this.goodsid=goodsid; this.qty=qty; this.addedtime=addedtime; } public int getid() { return id; } public void setid(int id) { this.id = id; } public string getshopper() { return shopper; } public void setshopper(string shopper) { this.shopper = shopper; } public int getgoodsid() { return goodsid; } public void setgoodsid(int goodsid) { this.goodsid = goodsid; } public int getqty() { return qty; } public void setqty(int qty) { this.qty = qty; } public date getaddedtime() { return addedtime; } public void setaddedtime(date addedtime) { this.addedtime = addedtime; } public goods getingoods() { return ingoods; } public void setingoods(goods ingoods) { this.ingoods = ingoods; } }
2.2.2.仍然是在cn.zuowenjun.boot.mapper包中定义数据操作接口(interface),注意这里只是定义接口,并不包含sql注解部份,因为这部份将在mapper的xml代码中进行配置实现,代码如下:
package cn.zuowenjun.boot.mapper; import java.util.list; import org.apache.ibatis.annotations.param; import cn.zuowenjun.boot.domain.*; public interface shoppingcartmapper { list<shoppingcart> getlist(string shopper); void insert(shoppingcart shoppingcart); void update(shoppingcart shoppingcart); void deleteitem(int id); void delete(string shopper); int getbuycount(string shopper); shoppingcart get(@param("shopper") string shopper,@param("goodsid") int goodsid); }
如上代码有一个重点说明:get方法有两个参数(多个参数也类似),为了mybatis能够自动映射到这些参数,必需为每个参数添加param注解,并指定参数名,这个参数名是与对应的mapper xml中的sql语句中定义的参数名相同。
2.2.3.在mybatis.mapper-locations设置的mapper xml存放的路径中创建xml文件,并手动编写映射的sql语句,如下所示:
<?xml version="1.0" encoding="utf-8"?> <!doctype mapper public "-//mybatis.org//dtd mapper 3.0//en" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="cn.zuowenjun.boot.mapper.shoppingcartmapper"> <resultmap id="shoppingcartmap" type="shoppingcart" > <id column="id" property="id" jdbctype="integer" /> <result column="shopper" property="shopper" jdbctype="nvarchar" /> <result column="goodsid" property="goodsid" jdbctype="integer" /> <result column="qty" property="qty" jdbctype="integer"/> <result column="addedtime" property="addedtime" jdbctype="date" /> <!-- referseee https://www.cnblogs.com/ysocean/p/7237499.html --> <association property="ingoods" javatype="cn.zuowenjun.boot.domain.goods"> <id column="id" property="id" jdbctype="integer" /> <result column="title" property="title" /> <result column="picture" property="picture" /> <result column="price" property="price" /> <result column="introduction" property="introduction" /> <result column="categoryid" property="categoryid" /> <result column="lasteditby" property="lasteditby" /> <result column="lastedittime" property="lastedittime" /> </association> </resultmap> <!-- 如果返回的结果与某个实体类完全相同,其实完全不需要上面的resultmap,而是直接使用resulttype=类名, 如:resulttype=cn.zuowenjun.boot.domain.shoppingcart(简写别名:shoppingcart),此处是示例用法,故采取指定映射 --> <select id="getlist" parametertype="string" resultmap="shoppingcartmap"> select * from ta_testshoppingcart a inner join ta_testgoods b on a.goodsid=b.id where shopper=#{shopper} order by addedtime </select> <select id="getbuycount" parametertype="string" resulttype="int"> select count(1) from (select goodsid from ta_testshoppingcart where shopper=#{shopper} group by goodsid) as t </select> <select id="get" resultmap="shoppingcartmap"> select * from ta_testshoppingcart a inner join ta_testgoods b on a.goodsid=b.id where shopper=#{shopper} and goodsid=#{goodsid} </select> <insert id="insert" parametertype="shoppingcart" usegeneratedkeys="true" keyproperty="id" keycolumn="id"> insert into ta_testshoppingcart(shopper, goodsid, qty, addedtime) values(#{shopper},#{goodsid},#{qty},getdate()) </insert> <update id="update" parametertype="shoppingcart" > update ta_testshoppingcart set shopper=#{shopper},goodsid=#{goodsid},qty=#{qty},addedtime=getdate() where id=#{id} </update> <delete id="deleteitem" parametertype="int"> delete from ta_testshoppingcart where id=#{id} </delete> <delete id="delete" parametertype="string"> delete from ta_testshoppingcart where shopper=#{shopper} </delete> </mapper>
如上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配置详见:
以上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包依赖,如下所示:
<!--sql server 数据驱动,以提供数据访问支持--> <dependency> <groupid>com.microsoft.sqlserver</groupid> <artifactid>mssql-jdbc</artifactid> <version>7.0.0.jre8</version><!--$no-mvn-man-ver$ --> </dependency> <!-- 添加mybatis生成器,以便通过maven build自动生成model、mapper及xml --> <dependency> <groupid>org.mybatis.generator</groupid> <artifactid>mybatis-generator-core</artifactid> <version>1.3.7</version> </dependency>
同时需要添加对应的maven插件,以便通过maven命令可执行生成过程,如下:(通过configurationfile元素指定生成器的配置路径,overwrite元素指定是否覆盖生成,这里有个坑,后面会介绍到,此处略)
<build> <plugins> <plugin> <!--ref: https://gitee.com/free/mybatis_utils/blob/master/mybatisgeneator/mybatisgeneator.md --> <!--ref: https://www.cnblogs.com/handsomeye/p/6268513.html --> <groupid>org.mybatis.generator</groupid> <artifactid>mybatis-generator-maven-plugin</artifactid> <version>1.3.7</version> <configuration> <configurationfile>src/main/resources/mybatis/generatorconfig.xml</configurationfile> <verbose>true</verbose> <overwrite>true</overwrite> </configuration> </plugin> </plugins> </build>
2.3.2.在cn.zuowenjun.boot.domain包中定义相关的数据实体模型类,我这里演示的类是:shoppingorder(购物订单信息),代码如下:
package cn.zuowenjun.boot.domain; import java.math.bigdecimal; import java.util.date; public class shoppingorder { private integer id; private string shopper; private integer totalqty; private bigdecimal totalprice; private boolean iscompleted; private string createby; private date createtime; public integer getid() { return id; } public void setid(integer id) { this.id = id; } public string getshopper() { return shopper; } public void setshopper(string shopper) { this.shopper = shopper == null ? null : shopper.trim(); } public integer gettotalqty() { return totalqty; } public void settotalqty(integer totalqty) { this.totalqty = totalqty; } public bigdecimal gettotalprice() { return totalprice; } public void settotalprice(bigdecimal totalprice) { this.totalprice = totalprice; } public boolean getiscompleted() { return iscompleted; } public void setiscompleted(boolean iscompleted) { this.iscompleted = iscompleted; } public string getcreateby() { return createby; } public void setcreateby(string createby) { this.createby = createby == null ? null : createby.trim(); } public date getcreatetime() { return createtime; } public void setcreatetime(date createtime) { this.createtime = createtime; } }
2.3.3.配置generatorconfig.xml,指定生成的各个细节,由于generatorconfig的配置节点比较多,如下只是贴出当前示例的配置信息,有一点要说明,注意配置节点的顺序,如果顺序不对就会报错,完整的配置方法详情介绍可参见:https://gitee.com/free/mybatis_utils/blob/master/mybatisgeneator/mybatisgeneator.md 或
<?xml version="1.0" encoding="utf-8"?> <!doctype generatorconfiguration public "-//mybatis.org//dtd mybatis generator configuration 1.0//en" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorconfiguration> <properties resource="application.properties" /> <!-- https://blog.csdn.net/zsy3313422/article/details/53190613 --> <classpathentry location="e:/localmvnrepositories/com/microsoft/sqlserver/mssql-jdbc/7.0.0.jre8/mssql-jdbc-7.0.0.jre8.jar" /> <context id="my" targetruntime="mybatis3simple" defaultmodeltype="flat"> <property name="javafileencoding" value="utf-8" /> <commentgenerator> <property name="suppressallcomments" value="true" /> <property name="suppressdate" value="true" /> </commentgenerator> <jdbcconnection driverclass="${spring.datasource.driverclassname}" connectionurl="${spring.datasource.url}" userid="${spring.datasource.username}" password="${spring.datasource.password}"> </jdbcconnection> <!-- 生成model实体类文件位置 --> <javamodelgenerator targetpackage="cn.zuowenjun.boot.domain" targetproject="src/main/java"> <property name="enablesubpackages" value="false" /> <property name="trimstrings" value="true" /> </javamodelgenerator> <!-- 生成mapper.xml配置文件位置 --> <!-- targetpackage这里指定包名,则会在如下的路径中生成多层级目录 --> <sqlmapgenerator targetpackage="mybatis.mapper" targetproject="src/main/resources"> <property name="enablesubpackages" value="false" /> </sqlmapgenerator> <!-- 生成mapper接口文件位置 --> <javaclientgenerator targetpackage="cn.zuowenjun.boot.mapper" targetproject="src/main/java" type="xmlmapper"> <property name="enablesubpackages" value="false" /> </javaclientgenerator> <table tablename="ta_testshoppingorder" domainobjectname="shoppingorder"> <generatedkey column="id" sqlstatement="jdbc" identity="true" /><!-- 指示id为自增id列,并在插入后返回该id --> </table> </context> </generatorconfiguration>
由于涉及的知识点比较多,在此就不作介绍,请参见我给出的链接加以了解。
2.3.4.通过maven 插件来执行生成代码(生成代码有很多种方法,详见:),这里我使用最为方便的一种,步骤如下:
项目右键-》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,代码如下:
package cn.zuowenjun.boot.mybatis.plugin; import java.util.list; import org.mybatis.generator.api.introspectedtable; import org.mybatis.generator.api.pluginadapter; import org.mybatis.generator.api.dom.java.interface; import org.mybatis.generator.api.dom.java.toplevelclass; import org.mybatis.generator.api.dom.xml.document; import org.mybatis.generator.codegen.mybatis3.javamapper.elements.abstractjavamappermethodgenerator; import org.mybatis.generator.codegen.mybatis3.xmlmapper.elements.abstractxmlelementgenerator; /* * 自定义通用可添加生成自定义方法插件类 * author:zuowenjun * date:2019-1-29 */ public abstract class customappendmethodplugin<te extends abstractxmlelementgenerator,tm extends abstractjavamappermethodgenerator> extends pluginadapter { protected final class<te> teclass; protected final class<tm> tmclass; @suppresswarnings("unchecked") public customappendmethodplugin(class<? extends abstractxmlelementgenerator> teclass, class<? extends abstractjavamappermethodgenerator> tmclass) { this.teclass=(class<te>) teclass; this.tmclass=(class<tm>) tmclass; } @override public boolean sqlmapdocumentgenerated(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); } @override public boolean clientgenerated(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); } @override public boolean validate(list<string> warnings) { // todo auto-generated method stub return true; } }
代码比较简单,主要是重写了sqlmapdocumentgenerated(生成mapper xml方法)、clientgenerated(生成mapper 接口方法),在这里面我通过把指定泛型类型(分别继承自 abstractxmlelementgenerator、abstractjavamappermethodgenerator)加入到生成xml和接口的过程中,以实现生成过程的抽象。
2.4.2.我这里由于默认生成的shoppingorderdetailmapper(实体类:shoppingorderdetail是购物订单详情)无法满足需要,我需要额外再增加两个方法:
list<shoppingorderdetail> selectbyorderid(int shoppingorderid); 、void deletebyorderid(int shoppingorderid); 故在这里自定义继承自customappendmethodplugin的插件类:shoppingorderdetailmapperplugin,具体实现代码如下:
package cn.zuowenjun.boot.mybatis.plugin; import java.util.set; import java.util.treeset; import org.mybatis.generator.api.dom.java.fullyqualifiedjavatype; import org.mybatis.generator.api.dom.java.interface; import org.mybatis.generator.api.dom.java.javavisibility; import org.mybatis.generator.api.dom.java.method; import org.mybatis.generator.api.dom.java.parameter; import org.mybatis.generator.api.dom.xml.attribute; import org.mybatis.generator.api.dom.xml.textelement; import org.mybatis.generator.api.dom.xml.xmlelement; import org.mybatis.generator.codegen.mybatis3.javamapper.elements.abstractjavamappermethodgenerator; import org.mybatis.generator.codegen.mybatis3.xmlmapper.elements.abstractxmlelementgenerator; /* * ref see https://www.cnblogs.com/se7end/p/9293755.html * author:zuowenjun * date:2019-1-29 */ public class shoppingorderdetailmapperplugin extends customappendmethodplugin<shoppingorderdetailxmlelementgenerator, abstractjavamappermethodgenerator> { public shoppingorderdetailmapperplugin() { super(shoppingorderdetailxmlelementgenerator.class,shoppingorderdetailjavamappermethodgenerator.class); } } class shoppingorderdetailxmlelementgenerator extends abstractxmlelementgenerator{ @override public void addelements(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 extends abstractjavamappermethodgenerator{ @override public void addinterfaceelements(interface interfaze) { if(!introspectedtable.getaliasedfullyqualifiedtablenameatruntime().equalsignorecase("ta_testshoppingorderdetail")) { return; } addinterfaceselectbyorderid(interfaze); addinterfacedeletebyorderid(interfaze); } private void addinterfaceselectbyorderid(interface interfaze) { // 先创建import对象 set<fullyqualifiedjavatype> importedtypes = new treeset<fullyqualifiedjavatype>(); // 添加lsit的包 importedtypes.add(fullyqualifiedjavatype.getnewlistinstance()); // 创建方法对象 method method = new method(); // 设置该方法为public method.setvisibility(javavisibility.public); // 设置返回类型是list fullyqualifiedjavatype returntype = fullyqualifiedjavatype.getnewlistinstance(); fullyqualifiedjavatype listargtype = new fullyqualifiedjavatype(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 void addinterfacedeletebyorderid(interface interfaze) { // 创建方法对象 method method = new method(); // 设置该方法为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实体类的代码:
package cn.zuowenjun.boot.domain; import java.math.bigdecimal; import java.util.date; public class shoppingorderdetail { private integer id; private integer shoppingorderid; private integer goodsid; private integer qty; private bigdecimal totalprice; private string createby; private date createtime; public integer getid() { return id; } public void setid(integer id) { this.id = id; } public integer getshoppingorderid() { return shoppingorderid; } public void setshoppingorderid(integer shoppingorderid) { this.shoppingorderid = shoppingorderid; } public integer getgoodsid() { return goodsid; } public void setgoodsid(integer goodsid) { this.goodsid = goodsid; } public integer getqty() { return qty; } public void setqty(integer qty) { this.qty = qty; } public bigdecimal gettotalprice() { return totalprice; } public void settotalprice(bigdecimal totalprice) { this.totalprice = totalprice; } public string getcreateby() { return createby; } public void setcreateby(string createby) { this.createby = createby == null ? null : createby.trim(); } public date getcreatetime() { return createtime; } public void setcreatetime(date createtime) { this.createtime = createtime; } }
另外顺便解决一个踩坑点:上面提到了,我们在pom文件配置mybatis-generator-maven-plugin插件时,overwrite设为true,目的是确保每次执行生成时,生成的代码能够覆盖已经存在的,理想是美好的,但现实总会有点小意外,我们这样配置,只能解决生成的mapper 接口类文件不会重复,但生成的mapper xml文件仍然会附加代码导致重复,故我们需要解决这个问题,而解决这个问题的关键是:generatedxmlfile.ismergeable,如果ismergeable为true则会合并,目前默认都是false,所以我们只需实现将generatedxmlfile.ismergeable设为true即可,由于ismergeable是私有字段,只能采取插件+反射动态改变这个值了,自定义合并代码插件overismergeableplugin实现如下:
package cn.zuowenjun.boot.mybatis.plugin; import java.lang.reflect.field; import java.util.list; import org.mybatis.generator.api.generatedxmlfile; import org.mybatis.generator.api.introspectedtable; import org.mybatis.generator.api.pluginadapter; /* * 修复mybatis-generator重复执行时生成的xml有重复代码(核心:ismergeable=false) * author:https://blog.csdn.net/zengqiang1/article/details/79381418 * editor:zuowenjun */ public class overismergeableplugin extends pluginadapter { @override public boolean validate(list<string> warnings) { return true; } @override public boolean sqlmapgenerated(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配置,如下:
<plugin type="cn.zuowenjun.boot.mybatis.plugin.overismergeableplugin"></plugin> <plugin type="cn.zuowenjun.boot.mybatis.plugin.shoppingorderdetailmapperplugin"></plugin> ... ...省略中间过程 <table tablename="ta_testshoppingorderdetail" domainobjectname="shoppingorderdetail"> <generatedkey column="id" sqlstatement="jdbc" identity="true" /> </table>
2.4.4.由于不能在同一个项目中直接使用plugin类(具体原因请上网查询,在此了解即可),故还需把cn.zuowenjun.boot.mybatis.plugin这个包中的文件单独导出生成jar包,然后把这个jar包复制到项目的指定目录下(本示例是放在libs目录下),然后再在pom为mybatis-generator-maven-plugin单独添加system本地依赖才行,maven添加依赖如下:
<plugin> <!--ref: https://gitee.com/free/mybatis_utils/blob/master/mybatisgeneator/mybatisgeneator.md --> <!--ref: https://www.cnblogs.com/handsomeye/p/6268513.html --> <groupid>org.mybatis.generator</groupid> <artifactid>mybatis-generator-maven-plugin</artifactid> <version>1.3.7</version> <configuration> <configurationfile>src/main/resources/mybatis/generatorconfig.xml</configurationfile> <verbose>true</verbose> <overwrite>true</overwrite> </configuration> <dependencies> <!-- 为mybatis-generator增加自定义插件依赖 --> <dependency> <groupid>cn.zuowenjun.boot.mybatis.plugin</groupid> <artifactid>cn.zuowenjun.boot.mybatis.plugin</artifactid> <version>1.0</version> <scope>system</scope> <systempath>${basedir}/src/main/libs/cn.zuowenjun.boot.mybatis.plugin.jar</systempath> </dependency> </dependencies> </plugin>
如果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,它们之间无需继承),代码如下:(很简单,就是定义了一个特殊用途的方法)
package cn.zuowenjun.boot.mapper; import java.util.list; import cn.zuowenjun.boot.domain.shoppingorder; public interface shoppingorderextmapper { list<shoppingorder> selectallbyshopper(string shopper); }
2.5.2.编写对应的shoppingorderextmapper.xml,这里面就要用到继承,继承主要是resultmap【实现继承用:extends=要继承的mapper xml resultmap】,这样就不用两个地方都为一个实体类写结果映射配置了,其余的都按一个新的mapper xml配置来设计即可,代码如下:
<?xml version="1.0" encoding="utf-8"?> <!doctype mapper public "-//mybatis.org//dtd mapper 3.0//en" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="cn.zuowenjun.boot.mapper.shoppingorderextmapper"> <resultmap id="baseresultmap" type="cn.zuowenjun.boot.domain.shoppingorder" extends="cn.zuowenjun.boot.mapper.shoppingordermapper.baseresultmap"> </resultmap> <select id="selectallbyshopper" resultmap="baseresultmap" parametertype="string"> select * from ta_testshoppingorder where shopper=#{shopper} </select> </mapper>
如上两步即完成扩展添加额外自定义的方法,又不用担心重复执行生成代码会被覆盖掉,只是使用时需要单独注册到spring,单独实例,虽不完美但弥补了默认生成代码的不足也是可行的。
2.6 使用springboottest + junit测试基于mybatis框架实现的dao类
在此不详情说明junit测试的用法,网上大把资源,只是单独说明结合springboottest 注解,完成单元测试,先看单元测试代码:
package cn.zuowenjun.springbootdemo; import java.math.bigdecimal; import java.util.date; import java.util.list; import org.junit.assert; import org.junit.test; import org.junit.runner.runwith; import org.springframework.beans.factory.annotation.autowired; import org.springframework.boot.test.context.springboottest; import org.springframework.test.annotation.rollback; import org.springframework.test.context.junit4.springrunner; import org.springframework.transaction.annotation.transactional; import cn.zuowenjun.boot.springbootdemoapplication; import cn.zuowenjun.boot.domain.*; import cn.zuowenjun.boot.mapper.goodsmapper; import cn.zuowenjun.boot.mapper.shoppingorderdetailmapper; import cn.zuowenjun.boot.mapper.shoppingordermapper; @runwith(springrunner.class) @springboottest(classes=springbootdemoapplication.class) public class shoppingordermappertests { @autowired private shoppingordermapper shoppingordermapper; @autowired private shoppingorderdetailmapper shoppingorderdetailmapper; @autowired private goodsmapper goodsmapper; @transactional @rollback(false) //不加这个,默认测试完后自动回滚 @test public void testinsertshoppingorder() { goods goods= goodsmapper.get(1); shoppingorder shoppingorder=new shoppingorder(); shoppingorder.setshopper("zuowenjun"); shoppingorder.setiscompleted(false); shoppingorder.settotalprice(bigdecimal.valueof(0)); shoppingorder.settotalqty(1); shoppingorder.setcreateby("zuowenjun"); shoppingorder.setcreatetime(new date()); int orderid= shoppingordermapper.insert(shoppingorder); shoppingorder.setid(orderid); shoppingorderdetail shoppingorderdetail=new shoppingorderdetail(); 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(new date()); shoppingorderdetailmapper.insert(shoppingorderdetail); list<shoppingorderdetail> 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); } }
与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配置如下:
<!-- 添加thymeleaf模板引擎(用于springmvc模式,如果是rest api项目,则无需引用) --> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-thymeleaf</artifactid> </dependency>
3.2.编写后端controller,以便响应用户请求,代码如下:(这个与普通spring mvc+jsp相同,区别在view)
package cn.zuowenjun.boot.controller; import java.util.list; import org.springframework.beans.factory.annotation.autowired; import org.springframework.stereotype.controller; import org.springframework.ui.model; import org.springframework.web.bind.annotation.getmapping; import org.springframework.web.bind.annotation.requestmapping; import cn.zuowenjun.boot.domain.*; import cn.zuowenjun.boot.service.*; @controller @requestmapping("/test") public class testcontroller { @autowired private shopuserservice shopuserservice; @getmapping("/userlist") public string list(model model) { list<shopuser> 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表达式,这个之前讲过的,很简单,不展开说明)
<!doctype html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="utf-8"> <title>test user list -power by thymeleaf</title> <style type="text/css"> table{ border:2px solid blue; border-collapse:collapse; width
相关文章:
-
-
bzoj3829 POI2014 FAR-FarmCraft
思路 用$f[i]$表示完成第$i$棵子树所需要得时间。 考虑如果有两个子树$a$和$b$,如果先去完成子树$a$,那么对于花费得时间就是$f[b... [阅读全文] -
一、使用Java NIO完成网络通信的三个核心 1.通道(Channel):负责连接 java.nio.channels.Channel 接口: |... [阅读全文]
-
Python中的可迭代对象/迭代器/For循环工作机制/生成器
本文分成6个部分: 1.iterable iterator区别 2.iterable的工作机制 3.iterator的工作机制 4.for循环的工作... [阅读全文] -
我们团队目前开发的产品是用java语言编写的,大家都知道,java编写的代码随便都可以被反编译,导致别人可能会看到你“裸奔”的样子。所以,为了避免这... [阅读全文]
-
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
上一篇: JavaScript 之 预编译 作用域,作用域链
下一篇: 用lambda表达式树优化反射
发表评论