使用Maven搭建Javaweb模块化项目
0 模板简介
本文记录了我在最近进行的今日指数项目中,如何从零开始搭建一个Java Web多模块项目,并集成MyBatis和Swagger。同时,还实现了一个根据用户名查询用户信息的简单功能。主要目的是通过规范项目目录结构,达到举一反三的效果。
1 创建父工程
模板由父子模块组成,parent模块负责管理版本,demo_common通用模块,demo_backend业务模块。
1.1 创建空的父工程
在Advanced Settings中填写项目信息。
GroupId:组织的唯一标识符,域名.公司名.项目名,org.apache.tomcat
ArtifactID: 项目的唯一的标识符, 项目名-xxx
在maven中通过GroupId和ArtifactID唯一确定一个jar包。
1.2 修改项目的maven地址
新建一个项目默认选择的是idea中自带的maven,我们修改为本地maven。
1.3 修改文件编码
1.4修改父工程的pom文件
1.4.1修改打包方式为pom
父工程不需要部署,只定义项目的依赖及版本。将项目打包方式修改为pom
<packaging>pom</packaging>
1.4.2.properties+dependencyManagement管理子工程的依赖版本
在properties中定义版本号(相当于全局变量),dependencyManagement中使用${},引入参数即可
dependencyManagement只做依赖的版本定义,并不直接导入依赖。
dependencyManagement中爆红解决方案
以下是模板使用的全局依赖定义,个人开发时根据实际情况删减。
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<maven.surefire.version>2.12.4</maven.surefire.version>
<mybatis-spring-boot-starter.version>2.1.4</mybatis-spring-boot-starter.version>
<pagehelper-spring-boot-starter.version>1.2.12</pagehelper-spring-boot-starter.version>
<mysql-driver.version>5.1.49</mysql-driver.version>
<fastjson.version>1.2.71</fastjson.version>
<springfox-swagger2.version>2.9.2</springfox-swagger2.version>
<druid-spring-boot-starter.version>1.1.22</druid-spring-boot-starter.version>
<druid-core-version>1.2.8</druid-core-version>
<sharding-jdbc.version>4.0.0-RC1</sharding-jdbc.version>
<jjwt.version>0.9.1</jjwt.version>
<easyExcel.version>3.0.4</easyExcel.version>
<xxl-job-core.version>2.3.0</xxl-job-core.version>
<spring-boot.version>2.5.3</spring-boot.version>
<joda-time.version>2.10.5</joda-time.version>
<google.guava.version>30.0-jre</google.guava.version>
<knif4j.version>2.0.2</knif4j.version>
<hutool.version>5.7.21</hutool.version>
<swagger.version>2.6.1</swagger.version>
<junit.version>3.8.1</junit.version>
</properties>
<!--定义依赖的版本,不负责依赖的引入,只负责版本的约定-->
<dependencyManagement>
<!--!!!易错点-->
<!--因为dependencyManagement标签只负责定义版本,不负责导入,如果maven仓库中没有指定的依赖,则报红色警告
解决方式:先通过dependencies标签下载资源,然后删除
-->
<dependencies>
<!--引入junit依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<!--引入springboot依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--引入mybatis场景依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot-starter.version}</version>
</dependency>
<!--pageHelper场景依赖-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper-spring-boot-starter.version}</version>
</dependency>
<!--mysql驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-driver.version}</version>
</dependency>
<!--shardingjdbc分库分表-->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>${sharding-jdbc.version}</version>
</dependency>
<!--json工具包-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!--druid-boot依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid-spring-boot-starter.version}</version>
</dependency>
<!--druid core-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid-core-version}</version>
</dependency>
<!--引入jwt依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
<!-- 导出 excel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>${easyExcel.version}</version>
</dependency>
<!--xxl-job定义任务框架支持-->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>${xxl-job-core.version}</version>
</dependency>
<!--时间小工具-->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>${joda-time.version}</version>
</dependency>
<!--引入google的工具集-->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${google.guava.version}</version>
</dependency>
<!--knife4j的依赖-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>${knif4j.version}</version>
</dependency>
<!--hutool万能工具包-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger.version}</version>
</dependency>
<!--swagger-ui支持类-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
1.4.3 统一插件管理
在父工程中定义项目的Maven插件版本。
<build>
<!--插件的版本锁定-->
<pluginManagement>
<plugins>
<!--Springboot核心插件-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<goals>
<!--防止打包时出现找不到springboot启动类的情况-->
<goal>repackage</goal>
</goals>
</execution>
</executions>
<configuration>
<excludes>
<!--插件运行时排除依赖-->
<exclude>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
1.4.4 pom整体结构
2 定义通用模块
定义一个common模块为项目中的其他模块提供服务。多个模块公用的依赖可以在common文件中引入,其他模块引入stock_common模块可以直接使用通用模块里引入的所有依赖。
2.1创建common模块
Name定义为 项目名_模块名
Parent选中父工程
Archetype的选择及效果
2.2定义pom.xml文件
2.2.1 修改打包方式为jar
<packaging>jar</packaging>
2.2.2 添加依赖
这里的依赖只需要定义<groupId> <artifactId> 即可,版本由父工程的pom文件控制。
<dependencies>
<!--省略类的getset方法-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--mybatis场景依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--jackson相关注解,实现日期格式转换和类型格式转换并序列化等-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<!--json工具包-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<!--jode日期插件-->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
<!--工具包-->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<!--分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!--apache工具包-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--配置提示,在写application.yml文件时提供提示-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<!--引入swagger依赖-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
</dependencies>
2.3 目录结构
├─constant 静态变量
├─mapper Mybatis对应的Mapper
├─pojo java普通对象,范围比较大,本质上就是一个数据容器,且无需提供复杂的方法(getter and setter)
│ ├─domain 领域对象,说白了就是将属于某个业务方向的数据加以封装形成的对象比如:以数据库为例,数据可能来自多张表、单表的部分字段值
│ ├─entity 实体对象,严格和数据库相对应
│ └─vo value object值对象,说白了就是保存值的对象,内置的属性之间不一定有强相关
└─utils 工具类
2.4 MybatisX逆向工程
MybatisX插件功能简介:https://developer.aliyun.com/article/1203863
结果如下
3 定义业务模块
3.1 创建demo_backend模块
仿照demo_common模块的创建方式,backend模块集成自parent模块,打包方式改为jar
3.2 修改pom文件
引入demo_common模块,springboot的基本依赖,测试依赖。
编译插件的版本在父工程中指定。
<dependencies>
<!--引入公共依赖-->
<dependency>
<groupId>org.example</groupId>
<artifactId>demo_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 基本依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--springboot测试依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<!--打包名称-->
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
3.3 模块目录
org
└─example
├─config 存放配置文件
├─controller 负责处理外部请求和响应。
├─service 负责业务逻辑的设计和实现
│ └─impl
└─vo View Object 与前端交互的数据对象
├─req request:用对象接收前端传来的数据
└─resp respose:将数据封装为对象并返回给前端
4 集成Mybatis
4.1 pom中引入依赖
mysql durid mybatis 依赖已经在demo_common的pom中引入。
4.2 配置
在application.yml中设置项目的启动端口,配置数据库连接池,mybatis的相关参数。
# 定义端口号
server:
port: 8091
# 配置数据源
spring:
# main:
# allow-bean-definition-overriding: true # 配置允许容器中的bean资源被覆盖。
datasource:
druid:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
driver-class-name: com.mysql.jdbc.Driver
# 初始化时建立物理连接的个数。初始化发生在显示调用 init 方法,或者第一次 getConnection 时
initialSize: 6
# 最小连接池数量
minIdle: 2
# 最大连接池数量
maxActive: 20
# 获取连接时最大等待时间,单位毫秒。配置了 maxWait 之后,缺省启用公平锁,
# 并发效率会 有所下降,如果需要可以通过配置 useUnfairLock 属性为 true 使用非公平锁。
maxWait: 60000
# 配置mybatis
mybatis:
type-aliases-package: org.example.pojo
mapper-locations: classpath:mapper/*.xml
configuration:
map-underscore-to-camel-case: true # 开启驼峰映射
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印运行的sql
cache-enabled: false # 禁止二级缓存
local-cache-scope: statement # 一级缓存默认开启session statement
Mybatis参数说明:
type-aliases-package: org.example.pojo
- 这个配置项指向了org.example.pojo包下的所有类,在MyBatis的映射文件中可以直接使用这些类的简写名,而不需要全限定名。例如,org.example.pojo.User可以直接用User
mapper-locations: classpath:mapper/*.xml
- 表示在类路径下的mapper文件夹中的所有XML文件,这些文件定义了SQL映射关系。
map-underscore-to-camel-case: true
- 将数据库中的下划线命名(如user_id)自动映射为Java对象中的驼峰命名(如userId)。由测试结果可以看到当直接打印从数据库中查找到的结果时,已经完成了驼峰转化过程,因此可以直接用sysUser接收。
<select id="selectByPrimaryKey1" resultType="org.example.pojo.entity.SysUser">
select * from sys_user where id = #{id}
</select>
//测试mybatis的驼峰转换
@Test
public void testCamel(){
System.out.println(sysUserMapper.selectByPrimaryKey1(1237361915165020161L));
}
JDBC Connection [com.mysql.jdbc.JDBC4Connection@1c4057f9] will not be managed by Spring
==> Preparing: select * from sys_user where id = ?
==> Parameters: 1237361915165020161(Long)
<== Columns: id, username, password, phone, real_name, nick_name, email, status, sex, deleted, create_id, update_id, create_where, create_time, update_time
<== Row: 1237361915165020161, admin, $2a$10$JqoiFCw4LUj184ghgynYp.4kW5BVeAZYjKqu7xEKceTaq7X3o4I4W, 13888888888, 小池, 超级管理员, [email protected], 1, 1, 1, null, 1237361915165020161, 1, 2019-09-22 19:38:05.0, 2020-04-07 18:08:52.0
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@401926df]
SysUser(id=1237361915165020161, username=admin, password=$2a$10$JqoiFCw4LUj184ghgynYp.4kW5BVeAZYjKqu7xEKceTaq7X3o4I4W, phone=13888888888, realName=小池, nickName=超级管理员, [email protected], status=1, sex=1, deleted=1, createId=null, updateId=1237361915165020161, createWhere=1, createTime=Sun Sep 22 19:38:05 CST 2019, updateTime=Tue Apr 07 18:08:52 CST 2020)
cache-enabled: false
二级缓存是跨SqlSession的,也就是说,不同的SqlSession可以共享二级缓存中的数据。
6 demo_backend模块
实现一个根据用户名查询用户信息的访问接口,完成接收前端访问请求并从数据库查询数据并返回的完整过程。
6.1 定义前后端交互数据格式
- 在前后端分离架构下,前后端使用json格式进行交互;
- 后端响应数据封装到统一类中;
{
code: 1 //状态码
msg: "" //提示信息
data: //任意响应数据
}
统一数据封装:
/**
* 返回数据类
* @JsonInclude 保证序列化json的时候,如果是null的对象,key也会消失
* @param <T>
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class R<T> implements Serializable {
private static final long serialVersionUID = 7735505903525411467L;
// 成功值,默认为1
private static final int SUCCESS_CODE = 1;
// 失败值,默认为0
private static final int ERROR_CODE = 0;
//状态码
private int code;
//消息
private String msg;
//返回数据
private T data;
private R(int code){
this.code = code;
}
private R(int code, T data){
this.code = code;
this.data = data;
}
private R(int code, String msg){
this.code = code;
this.msg = msg;
}
private R(int code, String msg, T data){
this.code = code;
this.msg = msg;
this.data = data;
}
public static <T> R<T> ok(){
return new R<T>(SUCCESS_CODE,"success");
}
public static <T> R<T> ok(String msg){
return new R<T>(SUCCESS_CODE,msg);
}
public static <T> R<T> ok(T data){
return new R<T>(SUCCESS_CODE,data);
}
public static <T> R<T> ok(String msg, T data){
return new R<T>(SUCCESS_CODE,msg,data);
}
public static <T> R<T> error(){
return new R<T>(ERROR_CODE,"error");
}
public static <T> R<T> error(String msg){
return new R<T>(ERROR_CODE,msg);
}
public static <T> R<T> error(int code, String msg){
return new R<T>(code,msg);
}
public static <T> R<T> error(ResponseCode res){
return new R<T>(res.getCode(),res.getMessage());
}
public int getCode(){
return code;
}
public String getMsg(){
return msg;
}
public T getData(){
return data;
}
}
ResponseCode:枚举类状态码
public enum ResponseCode{
ERROR(0,"操作失败"),
SUCCESS(1,"操作成功"),
DATA_ERROR(0,"参数异常"),
NO_RESPONSE_DATA(0,"无响应数据"),
CHECK_CODE_NOT_EMPTY(0,"验证码不能为空");
private int code;
private String message;
private ResponseCode(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
6.2 集成swagger+knife4j
官网:https://swagger.io/
knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案
6.2.1 demo_common工程引入依赖
<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
<!--knife4j的依赖-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
<!--支持接口参数校验处理,表单验证工具-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
6.2.2 demo_backend工程config包定义swagger配置类
@Configuration
@EnableSwagger2
@EnableKnife4j
@Import(BeanValidatorPluginsConfiguration.class)
public class SwaggerConfiguration {
@Bean
public Docket buildDocket() {
//构建在线API概要对象
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(buildApiInfo())
.select()
// 要扫描的API(Controller)基础包
.apis(RequestHandlerSelectors.basePackage("org.example.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo buildApiInfo() {
//网站联系方式
Contact contact = new Contact("mmj","https://www.test.com/","[email protected]");
return new ApiInfoBuilder()
.title("文档标题")//文档标题
.description("文档描述信息")//文档描述信息
.contact(contact)//站点联系人相关信息
.version("1.0.0")//文档版本
.build();
}
}
6.2.3 生成swagger注解
https://blog.csdn.net/m0_61466807/article/details/128430020
访问在线文档:http://localhost:8091/doc.html
6.2 Controller层
@Api(value = "/api", tags = {"test"})
@RestController
@RequestMapping("/api")
public class UserController {
@Autowired
private UserService userService;
/**
* 根据用户名查询用户信息
* @param userName
* @return
*/
@ApiImplicitParams({
@ApiImplicitParam(paramType = "path", dataType = "string", name = "userName", value = "", required = true)
})
@ApiOperation(value = "根据用户名查询用户信息", notes = "根据用户名查询用户信息", httpMethod = "GET")
@GetMapping("/user/{userName}")
public R<SysUser> getUserByUserName(@PathVariable("userName") String userName){
return userService.getUserByUserName(userName);
}
}
@RestController = @Controller + @ResponseBody
用于返回json格式的数据。
6.3 service层
public interface UserService{
/**
* 根据用户查询用户信息
* @param userName 用户名称
* @return
*/
R<SysUser> getUserByUserName(String userName);
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private SysUserMapper sysUserMapper;
/**
* 根据用户名称查询用户信息
*
* @param userName 用户名称
* @return
*/
@Override
public R<SysUser> getUserByUserName(String userName) {
SysUser user=sysUserMapper.findByUserName(userName);
if(user == null) return R.error(ResponseCode.USERNAME_NOT_EXISTS);
return R.ok(user);
}
}
6.4 DAO层
SysUserMapper
public interface SysUserMapper {
/**
* 根据用户名称查询用户信息
* @param userName 用户名称
* @return
*/
SysUser findByUserName(String userName);
}
SysUserMapper.xml
<select id="findByUserName" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from sys_user
where username= #{name}
</select>
6.5 测试
启动项目访问 localhost:8091/api/user/admin
访问在线文档:http://localhost:8091/doc.html