springmvc、jpa(hibernate)、mybatis组合框架搭建

前言

为什么要搭建这么个组合呢?这个问题,其实我思考了很久。说实话,最干净的搭建,我个人还是倾向于搭建springmvc+mybatis的组合。但是呢,用mybatis去写各种细小的操作重复性质的sql,实体间关系,复杂对象的加载啥的,真的是很浪费时间。什么?代码生成器?我个人是抵制代码生成器这种东西的,于是,一点探索的经验都没有,严重抵制生成出来的一堆垃圾,没人改,因为省事,于是更倾向于生成啥的,生成后各种维护没人管啥的,我宁愿用hibernate。但是,因为之前经历过hibernate的自动维护数据库,不靠谱,这次只打算搭建让hibernate检查实体是否和数据库映射正确这件事。但是,hibernate的问题,也很大,其实主要出在复杂结果的查询。不管是否满足,业务对象我们习惯性延迟加载,这种对象即使满足复杂查询结果,我们也应该单独建立DO或者VO来应对复杂结果查询,因为两个业务变化独立性和延迟加载带来的查询效率低下。而且,hibernate写业务操作,真的挺快的。写的sql优化,后面再说吧,先让我用这个撑着。

搭建

创建项目

这事吧,就是个干净的maven项目,这里我甚至没有使用任何骨架,从头来吧。

-Dmaven.multiModuleProjectDirectory system propery is not set. Check $M2_HOME environment variable and mvn script match.

添加M2_HOME的环境变量
  在运行maven的VM参数里添加-Dmaven.multiModuleProjectDirectory=$M2_HOME。idea的是在setting->maven->Runner中找到的。

springmvc搭建

版本

这里选用sprin4的最后一个版本4.3.10.RELEASE,其他组件也使用这个版本

依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${version.spring}</version>
</dependency>

文件结构

springmvc、jpa(hibernate)、mybatis组合框架搭建

建立基础的文件结构,如上图所示:

  • 一个controller
  • spring的相关配置xml
  • properties文件
  • web.xml

先大概说一下这些都是干什么的。其实,最核心的,是这个web.xml。这个东西,是遵循啥规范来着,忘了,反正必须建在这么个目录下面,叫这个名字。应用服务器会解析里面的servlet配置,从而开始我们的web应用。我的里面的内容是酱紫的:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
        http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <display-name>sp</display-name>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring/spring-context.xml</param-value>
    </context-param>
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring/spring-context.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>


</web-app>
如上代码所见,我们引入了spring-context.xml,其实吧,可以直接引入sprimg-mvc.xml的,但是,因为我会有好多spring的配置xml,而且,我只想在这一个地方引入,就在这里引入所有的,所以我建立了一个spring-context.xml,用它来管理所有的spring的xml。

spring-context.xml如下:

<?xml version="1.0" encoding="UTF-8"  ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jpa="http://www.springframework.org/schema/data/jpa"
       xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.0.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/task
        http://www.springframework.org/schema/task/spring-task-3.1.xsd  ">

    <context:component-scan base-package="nature.ntbrick.demo.smh" />
    <context:property-placeholder location="classpath:applicatoin.properties"/>

    <import resource="./spring-mvc.xml"/>
</beans>
可以看到,我在这里定位了spring扫描的基础包名,它包含了我所有的java文件,所以后面我所有的spring的xml都不用配置该项了。我们还引入了properties文件,和spring-mvc.xml

spring-mvc.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <mvc:annotation-driven />

</beans>

其实吧,就是开启了注解驱动。酱紫我们就能用Controller呀,Service呀这些注解来配置项目了。然后我们来看看DemoController里是啥样的。

@Controller
@RequestMapping("/demo")
public class DemoController {
    @ResponseBody
    @RequestMapping("/ping")
    public String ping(){
        return "pong";
    }
}

至此,我们已经搭建一起个基本的mvc框架,可以通过url访问controller中的内容。需要注意的是,我们这次搭建的是springmvc项目,所以,请在pom中将打包类型定义为war

添加mybatis

引入mybatis

添加mybatis,我们首先要做的是引入mybatis相关的依赖。在pom文件中添加如下依赖。mybatis我们这次用的版本是3.4.5的。

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-jdbc</artifactId>
	<version>${version.spring}</version>
</dependency>
<dependency>
	<groupId>org.mybatis</groupId>
	<artifactId>mybatis</artifactId>
	<version>${version.mybatis}</version>
</dependency>
<dependency>
	<groupId>org.mybatis</groupId>
	<artifactId>mybatis-spring</artifactId>
	<version>1.3.0</version>
</dependency>
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<version>5.1.34</version>
</dependency>
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>druid</artifactId>
	<version>1.0.7</version>
</dependency>

配置mybatis

首先,我们要加入mybatis的spring配置文件spring-mybatis.xml,然后再spring-context.xml里引入它,参照上次的spring-mvc.xml的引入就好。以下,是这个文件里的内容。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-3.1.xsd
                        http://www.springframework.org/schema/mvc
                        http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">

    <!-- 数据源 org.apache.commons.dbcp.BasicDataSource com.alibaba.druid.pool.DruidDataSource -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">

        <property name="driverClassName" value="${jdbc.driver}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />


        <!-- 初始化连接大小 -->
        <property name="initialSize" value="0" />
        <!-- 连接池最大使用连接数量 -->
        <property name="maxActive" value="20" />
        <!-- 连接池最小空闲 -->
        <property name="minIdle" value="0" />
        <!-- 获取连接最大等待时间 -->
        <property name="maxWait" value="60000" />


        <property name="validationQuery"><value>SELECT 1</value></property>
        <property name="testOnBorrow" value="false" />
        <property name="testOnReturn" value="false" />
        <property name="testWhileIdle" value="true" />

        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="6000" />
        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="25200000" />

        <!-- 打开removeAbandoned功能 -->
        <property name="removeAbandoned" value="true" />
        <!-- 1800秒,也就是30分钟 -->
        <property name="removeAbandonedTimeout" value="1800" />
        <!-- 关闭abanded连接时输出错误日志 -->
        <property name="logAbandoned" value="true" />

        <!-- 监控数据库 -->
        <!-- <property name="filters" value="stat" /> -->
        <!--<property name="filters" value="mergeStat" />-->
    </bean>

    <!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!-- 自动扫描mapping.xml文件 -->
        <property name="mapperLocations" value="classpath:mybatis/mapper/**/*.xml"></property>
    </bean>

    <!-- DAO接口所在包名,Spring会自动查找其下的类 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="nature.ntbrick.demo.smh.dao" />
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
    </bean>

    <!-- (事务管理)transaction manager, use JtaTransactionManager for global tx -->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

</beans>
    可以看到的是,首先是对dataSource数据源的配置,这里我们用的是阿里的druid的数据源,原因就是网上盛传它最快,具体再测吧。这里有个很深的坑,坑了我很久,注意变量,尤其是${jdbc.username},这里我之前是${username},本来说贪省事,加多的配置再改,结果坑了很久,注意${username}这个变量拿的是当前系统的登录用户的用户名。所以,我都加了jdbc前缀,就都拿到我自己配的值了。
    然后大家看下面的sqlSessionFactory的配置,里面mapperLocation的配置,指明了我扫描对应的mapper的配置文件的地方,所以我这里建立了resources/mybatis/mapper目录,里面放了demoMapper.xml文件。
    后面org.mybatis.spring.mapper.MapperScannerConfigurer配置了我去哪里扫描dao接口文件,然后,用谁来解析dao接口
    transactionManager是事务的配置,在这个demo里面没有用到。
    为了支撑这段配置,我们需要建立对应的到接口,mapper.xml,maven依赖。我们建立了nature.demo.dao.DemoDao和demoMapper.xml

nature.ntbrick.demo.smh.dao.DemoDao如下:

package nature.ntbrick.demo.smh.dao;

import java.util.List;
import java.util.Map;

public interface DemoDao {
    public List<Map<String,Object>> demoSelect();
}

demoMapper.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="nature.ntbrick.demo.smh.dao.DemoDao">
    <select id="demoSelect" resultType="java.util.HashMap">
    select * from demo
  </select>
</mapper>

nature.properties中加入的属性配置如下:


jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/test
jdbc.username=root
jdbc.password=123456

至此,我们已经可以访问数据库了。然后,我们在之前的DemoController中加入对应的方法来访问数据库,如下:


@Controller
@RequestMapping("/demo")
public class DemoController {

    @Resource
    private DemoDao demoDao;

    @ResponseBody
    @RequestMapping("/ping")
    public String ping(){
        return "pong";
    }

    @ResponseBody
    @RequestMapping("/demoselect")
    public List<Map<String,Object>> demoSelect(){
        return demoDao.demoSelect();
    }
}

    如上面代码,我们加了demoSelect。如果这个时候,你访问http://localhost:8080/demo/demoselect ,会发现,数据查出来了,但是,无法返回,会报错。因为,无法将List<Map<String,Object>>转化成json。需要加入一段配置,让@ResponseBody可以正常的将结果实体转化成json。我们在spring-mvc.xml中加入如下配置:
  <!--使用jsonconverter,避免IE执行AJAX时,返回JSON出现下载文件 -->
    <bean id="mappingJackson2HttpMessageConverter"
          class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
        <property name="supportedMediaTypes">
            <list>
                <bean class="org.springframework.http.MediaType">
                    <constructor-arg index="0" value="text"/>
                    <constructor-arg index="1" value="plain"/>
                    <constructor-arg index="2" value="UTF-8"/>
                </bean>
            </list>
        </property>
    </bean>
    <!--接口返回中文乱码问题-->
    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>application/json;charset=UTF-8</value>
                        <value>text/html;charset=UTF-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
    网上查到的很多资料用的配置Converter的方法,用到的类都是过期的,最后找到这个,有效,并且未过期,具体未研究,再说吧,后面对返回值格式控制的时候再说吧。

需要加入的依赖如下:

<!-- 使用Json所依赖的jar包 -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.5.4</version>
</dependency>

至此,我们已经成功的添加了mybatis相关的操作,并可以将数据添加到http请求的返回值的body中传回了。

引入hibernate

添加依赖

我们需要将对应的依赖引入pom文件。

<!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-jpa -->
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-jpa</artifactId>
        <version>1.11.6.RELEASE</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-entitymanager -->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>3.6.10.Final</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.hibernate.javax.persistence/hibernate-jpa-2.0-api -->
    <dependency>
        <groupId>org.hibernate.javax.persistence</groupId>
        <artifactId>hibernate-jpa-2.0-api</artifactId>
        <version>1.0.1.Final</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-core -->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>3.6.10.Final</version>
    </dependency>

解释下,首先jpa,是由spring-data-jpa支持的,根据自己的spring版本选择对应的版本引入。然后可以在仓库的信息中看到hibernate-entitymanager和hibernate-jpa-2.0-api的依赖,其余的都是spring的依赖,引入后发现还依赖于hibernate-core,选择对应版本引入后,先这样。这是资料可以查到的最小依赖集合,后面缺,再补。

配置

加入spring-jpa.xml,然后把这个文件引入spring-context.xml中。spring-jpa.xml的内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"

       xmlns:jpa="http://www.springframework.org/schema/data/jpa"
       xsi:schemaLocation="
                    http://www.springframework.org/schema/beans
                    http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
                    http://www.springframework.org/schema/tx
                    http://www.springframework.org/schema/tx/spring-tx-3.1.xsd

                    http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"
       default-lazy-init="true">

    <!-- JPA实体管理器工厂 -->
    <bean id="entityManagerFactory"
          class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter" />
        <!-- 加入定制化包路径 -->
        <property name="packagesToScan" value="nature.ntbrick.demo.smh" />

        <property name="jpaProperties">
            <props>
                <prop key="hibernate.current_session_context_class">thread</prop>
                <prop key="hibernate.hbm2ddl.auto">validate</prop><!-- validate/update/create -->
                <prop key="hibernate.show_sql">true</prop>
                <prop key="hibernate.format_sql">true</prop>

                <!-- 建表的命名规则 -->
                <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>

            </props>
        </property>
    </bean>

    <!-- 设置JPA实现厂商的特定属性 -->
    <bean id="hibernateJpaVendorAdapter"
          class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
        <property name="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect"/>
    </bean>

    <!-- Jpa 事务配置 -->
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>

    <!-- Spring Data Jpa配置 -->
    <jpa:repositories base-package="nature.ntbrick.demo.smh"  transaction-manager-ref="transactionManager" entity-manager-factory-ref="entityManagerFactory"/>

    <!-- 使用annotation定义事务 -->
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />
</beans>

说实话,这里的配置我也是从网上抄来的,然后删改了一部分。首先,datasource部分,我们重用了mybatis的配置,这里就没有重新来了,今天我们先把jpa引进来,后续还要观察mybatis和hibernate在同一事务下混合使用的表现。这里有基础的事务配置,我还没验证。后续我还会重新整理配置的结构,把datasource和transaction进行集中,否则很多东西扯不清楚,今天就先这样。
  需要注意的是hibernate的hibernate.hbm2ddl.auto,我这里配的是validate,不太信得过它的自动更新,之后会后续跟进对应的创建数据库的方法,至于维护,就团队自己想办法吧。
  注意配置包的范围,在这里坑了一小会儿。

编码

我们这里引入了JpaDemoDao,TestJpa,DemoJpaService三个类来完成本次的样例,很简单,我们就写一个实体,然后在service里用dao插进去。

实体类TestJpa

package nature.ntbrick.demo.smh.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Date;

/**
 * 测试实体类
 *
 * @author nature
 * @create 2018-03-04 23:08
 */
@Entity
@Table(name="test_jpa")
public class TestJpa  implements Serializable {

	private static final long serialVersionUID = -3421148517857545609L;

	@Id
	@Column(name="id")
	private Long id;

	@Column(name="name")
	private String name;

	@Column(name="createtime")
	private Date createtime;

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Date getCreatetime() {
		return createtime;
	}

	public void setCreatetime(Date createtime) {
		this.createtime = createtime;
	}
}


JpaDemoDao

package nature.ntbrick.demo.smh.dao.jpademo;

import nature.ntbrick.demo.smh.entity.TestJpa;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * @author nature
 * @create 2018-03-04 23:09
 */
public interface JpaDemoDao extends JpaRepository<TestJpa,Long> {
}


DemoJpaService

package nature.ntbrick.demo.smh.service;

import nature.ntbrick.demo.smh.dao.jpademo.JpaDemoDao;
import nature.ntbrick.demo.smh.entity.TestJpa;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author nature
 * @create 2018-03-04 23:10
 */
@Service
public class DemoJpaService {

	@Resource
	private JpaDemoDao jpaDemoDao;

	public void addRecord(TestJpa record){

		jpaDemoDao.save(record);
	}
}


至此,代码的编码就完结了。

单元测试

上面我们进行了编码,但是,如果每次写代码都需要写出对应的controller,然后再模拟http请求,才能够进行测试,那么,我们编码的效率将会是低下的。因为想证明我们是否正确,我们几乎需要等待一个模块编码完成。单元测试,可以帮我们解决这个问题。

添加依赖

我们使用junit进行单元测试。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>${version.spring}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

编写测试

我们建立一个和service同名的包,但是是建在test文件夹下,然后建立测试类,代码如下。

package nature.ntbrick.demo.smh.service;

import nature.ntbrick.demo.smh.entity.TestJpa;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.Date;

/**
 * @author nature
 * @create 2018-03-04 23:16
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value="classpath:spring/spring-context.xml")
public class DemoJpaServiceTest {
	@Autowired
	private DemoJpaService demoJpaService;

	@Test
	public void testAddRecord(){

		TestJpa testJpa=new TestJpa();
		testJpa.setId(1L);
		testJpa.setName("test2");
		testJpa.setCreatetime(new Date());
		demoJpaService.addRecord(testJpa);
	}
}

然后,你运行这个单元测试,他就会报错啦,原因呢,就是找不到servlet类,因为我们用tomcat启动的时候,它有,但是,我们自己做单元测试的时候没有,引入依赖:

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>

好啦,打完收工。兄弟些,你成功了。

附录

  • demo的源码在https://gitee.com/naturetrible/ntbrick.demo