SpringBoot开发记录

本文详述SpringBoot结合MyBatis的项目初始化、代码自动生成、查询操作、事务管理、Druid监控、RESTful设计、Swagger集成及异常处理等关键环节,提供丰富的实践案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1.快速初始化

2.pom.xml文件

3.Mybatis自动生成代码

3.1 新建generatorCOnfig文件

3.2 pom.xml文件中添加插件

3.3 开始生成

3.4 使用

4.查询样例

4.1 普通单表操作

4.2 自增主键表的操作

4.3 联合查询

4.4 分页查询

5.关于事物

5.1 关于@EnableTransactionManagement注解

5.2 关于@Transactional注解的位置

5.3 关于try/catch

5.4 关于事物传播

6.druid监控

7.Controller

8.Swagger

8.1 添加maven依赖

8.2 添加自动配置类

8.3 给接口配置说明

8.4 界面化接口说明文档

9.关于restful

10.关于全局异常捕获

参考文献


1.快速初始化

SpringBoot基础工程的搭建,可以通过这里在线界面化配置生成,生成后,直接本地导入即可。

其中,Dependencies中点击See all可以选择需要一类的jar包,一般情况下,可以选择以下几个:

选完之后,点击Generate Project 就生成基础工程了,下载到本地,导入到idea即可。

2.pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.tydic</groupId>
    <artifactId>cloud_wind</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>cloud_wind</name>
    <description>Demo project for Spring Boot</description>
    <!--打包方式,可以配置成jar或者war-->
    <packaging>jar</packaging>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.29</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <!--添加mybatis generator maven插件,自动生产mybatis对应的实体类-->
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <configuration>
                    <!--generatorConfig.xml文件位置-->
                    <configurationFile>src/main/resources/mybatis-generator/generatorConfig.xml</configurationFile>
                    <verbose>true</verbose>
                    <overwrite>true</overwrite>
                </configuration>
                <executions>
                    <execution>
                        <id>Generate MyBatis Artifacts</id>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                        <phase>generate-sources</phase>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>5.1.44</version>
                        <scope>runtime</scope>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
</project>

有些依赖是后面添加进去的,例如druid相关包。

3.Mybatis自动生成代码

Mybatis是需要通过xml和Mapper映射来完成正常使用,以前使用hibernate的时候,可以通过eclipse自动逆向工程生成各个表的hbm文件,Mybatis也提供的有类似的功能,通过maven插件来自动生成每一个表的实体类、对应的Mapper以及xml文件。

关于Mybatis Generator配置文件的详细介绍,参考这里,博主写的非常详细,下面根据需要我在项目中的使用如下。

3.1 新建generatorCOnfig文件

在resources目录下新建mybatis-generator目录,然后新建generatorConfig.xml文件,内容如下:

<?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>
    <!--执行generator插件生成文件的命令: call mvn mybatis-generator:generate -e -->
    <!--classPathEntry:数据库的JDBC驱动,换成你自己的驱动位置 可选 -->
    <!--<classPathEntry location="E:\mybatis\mysql-connector-java-5.1.24-bin.jar" /> -->

    <!-- 一个数据库一个context -->
    <!--defaultModelType="flat" 大数据字段,不分表 -->
    <context id="MysqlTables" targetRuntime="MyBatis3Simple" defaultModelType="flat">
        <!-- 自动识别数据库关键字,默认false,如果设置为true,根据SqlReservedWords中定义的关键字列表;
        一般保留默认值,遇到数据库关键字(Java关键字),使用columnOverride覆盖 -->
        <property name="autoDelimitKeywords" value="true"/>
        <!-- 生成的Java文件的编码 -->
        <property name="javaFileEncoding" value="utf-8"/>
        <!-- beginningDelimiter和endingDelimiter:指明数据库的用于标记数据库对象名的符号,比如ORACLE就是双引号,MYSQL默认是`反引号; -->
        <property name="beginningDelimiter" value="`"/>
        <property name="endingDelimiter" value="`"/>

        <!-- 格式化java代码 -->
        <property name="javaFormatter" value="org.mybatis.generator.api.dom.DefaultJavaFormatter"/>
        <!-- 格式化XML代码 -->
        <property name="xmlFormatter" value="org.mybatis.generator.api.dom.DefaultXmlFormatter"/>
        <plugin type="org.mybatis.generator.plugins.SerializablePlugin"/>
        <plugin type="org.mybatis.generator.plugins.ToStringPlugin"/>

        <!-- 注释 -->
        <commentGenerator>
            <property name="suppressAllComments" value="false"/><!-- 是否取消注释 -->
            <property name="suppressDate" value="true"/> <!-- 是否生成注释代时间戳-->
        </commentGenerator>

        <!-- jdbc连接 -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://130.76.53.161:3306/customer"
                        userId="root"
                        password="root"/>
        <!-- 类型转换 -->
        <javaTypeResolver>
            <!-- 是否使用bigDecimal, false可自动转化以下类型(Long, Integer, Short, etc.) -->
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>

        <!-- 生成实体类地址 -->
        <javaModelGenerator targetPackage="com.tydic.cloud_wind.models" targetProject="src/main/java">
            <property name="enableSubPackages" value="false"/>
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>
        <!-- 生成mapxml文件 -->
        <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources">
            <property name="enableSubPackages" value="false"/>
        </sqlMapGenerator>
        <!-- 生成mapxml对应client,也就是接口dao -->
        <javaClientGenerator targetPackage="com.tydic.cloud_wind.dao" targetProject="src/main/java" type="XMLMAPPER">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>
        <!-- table可以有多个,每个数据库中的表都可以写一个table,tableName表示要匹配的数据库表,也可以在tableName属性中通过使用%通配符来匹配所有数据库表,只有匹配的表才会自动生成文件 -->
        <!--如果想生成一个表则tableName="table_name"-->
        <table tableName="%"
               enableCountByExample="true"
               enableUpdateByExample="true"
               enableDeleteByExample="true"
               enableSelectByExample="true"
               selectByExampleQueryId="true">
            <property name="useActualColumnNames" value="false"/>
        </table>
    </context>
</generatorConfiguration>

我在用的时候有几个疑惑点:

(1)context的targeRuntime是什么,一定要用MyBatis3Simple吗?

不单单有MyBatis3SImple,还有MyBatis3,区别在于:

  targetRuntime:
        1,MyBatis3:默认的值,生成基于MyBatis3.x以上版本的内容,包括XXXBySample;
        2,MyBatis3Simple:类似MyBatis3,只是不生成XXXBySample;

(2)如果后面有表结构新增了,我能不能一个表一个表的添加?

可以,在配置文件中,关于table的配置,可以单独配置独立的表,如下所示:

<table tableName="user_info" domainObjectName="UserInfo"></table>

注意修改targetProject中对应的目录,以及对应的文件夹需要提前创建好,例如:mapper/models/dao在相关包或者目录下的文件夹。

(3)假设数据库表设计的不规范,有些字段名称和java的关键字一样,例如:有字段名为public,此时生成的实体类就会有错,怎么解决?

可以通过再table中配置columnOverride来解决这个问题

 <table tableName="%"
               enableCountByExample="true"
               enableUpdateByExample="true"
               enableDeleteByExample="true"
               enableSelectByExample="true"
               selectByExampleQueryId="true">
        <columnOverride column="public" property="isPublic" />
 </table>

3.2 pom.xml文件中添加插件

添加在build下的plugins下,内容如下

<!--添加mybatis generator maven插件,自动生产mybatis对应的实体类-->
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <configuration>
                    <!--generatorConfig.xml文件位置-->
                    <configurationFile>src/main/resources/mybatis-generator/generatorConfig.xml</configurationFile>
                    <verbose>true</verbose>
                    <overwrite>true</overwrite>
                </configuration>
                <executions>
                    <execution>
                        <id>Generate MyBatis Artifacts</id>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                        <phase>generate-sources</phase>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>5.1.44</version>
                        <scope>runtime</scope>
                    </dependency>
                </dependencies>
            </plugin>

主要是指定generatorConfig配置文件的位置。

3.3 开始生成

有两种操作方式,第一种是在idea中通过界面化操作来完成,如下:

点击按钮之后,就能看到每个目录下的已经生成的文件的,一个表对应一个dao、Mapper、xml文件。

也可以直接使用maven命令,命令如下
 

mvn mybatis-generator:generate

3.4 使用

相关代码生成之后,其实还没有和SpringBoot建立联系,因为其实相关实体类及映射文件的生成,其实和Springboot框架自身并没有关系,单独使用maven也可以生成,所以,在生成相关文件之后,我们还需要有一些其他配置。

(1)在启动类添加注解

/**
 * 配置MapperScan就不用再每一個Mapper文件上添加@Mapper的注解了
 */
@SpringBootApplication
@MapperScan("com.tydic.cloud_wind.dao")
public class CloudWindApplication {
	public static void main(String[] args) {
		SpringApplication.run(CloudWindApplication.class, args);
	}

}

(2)指定mapper的映射文件

在application.properties中添加如下一行设置

#指定mapper映射文件位置
mybatis.mapper-locations=classpath:/mapper/*Mapper.xml
#指定models的位置,如果在mapper.xml文件中的resultType指定了具体的包路径,这里就可以不用设置
#mybatis.type-aliases-package=com.tydic.cloud_wind.models

关于mybatis.type-aliases-package的配置,如果是通过自动生成的代码,是不需要配置的,因为我们随意打开一个xml文件,可以看到其namespace指定的都是具体的包路径下的Mapper文件,但是有时候,我们可以还会自定义一些Mapper文件和xml文件,此时如果namespace仅仅指定类名,而不指定具体的包路径,就需要在application.properties中配置mybatis.type-aliases-package,这样xml文件就能和Mapper文件自动建立映射。

4.查询样例

编写查询相关代码之前,我们还需要先设置mybatis的数据源相关信息,编辑application.properties,如下:

#数据库配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql://192.168.1.120:3306/customer?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root
#spring.datasource.filters=stat,wall,log4j

#mybatis配置
#开启二级缓存 一级缓存是mapper级别,二级缓存是namespaces级别的,所谓缓存就是相同查询不走数据库,除非触发了清楚缓存动作才会重新查询
mybatis.configuration.cache-enabled=true
mybatis.configuration.default-statement-timeout=3000
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.use-generated-keys=true
#指定mapper映射文件位置
mybatis.mapper-locations=classpath:/mapper/*Mapper.xml
#指定models的位置,如果在mapper.xml文件中的resultType指定了具体的包路径,这里就可以不用设置
#mybatis.type-aliases-package=com.tydic.cloud_wind.models
#开启打印sql查询日志的功能
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

4.1 普通单表操作

在dao下对应每一个表生成的Mapper文件,都有如5个方法:

public interface DdMsgPushMapper {
    int deleteByPrimaryKey(Integer id);
    int insert(DdMsgPush record);
    DdMsgPush selectByPrimaryKey(Integer id);
    List<DdMsgPush> selectAll();
    int updateByPrimaryKey(DdMsgPush record);
}

我们在使用的时候,可以非常方便的通过需要构造对应的实体类,然后完成对单表的增删改查,下面看几个简单的例子,在service包下新增一个MybatisService的类,部分样例查询代入如下:

@Service
@Transactional
public class MybatisService
{
    @Autowired
    private TmpDispUserMapper   tMapper;

    //简单查询-直接查询全部
    public void simpleSelect()
    {
        List<TmpDispUser> rList = tMapper.selectAll();
        System.out.println(rList.size());
        for (TmpDispUser tU : rList)
        {
            System.out.println(tU.toString());
        }
    }

    //简单查询-根据id查询
    public void findUserById(Integer id)
    {
        TmpDispUser tUser = tMapper.selectByPrimaryKey(id);
        System.out.println(tUser.toString());
    }

    //正常新增
    public void addUser()
    {
        TmpDispUser tUser = new TmpDispUser();
        tUser.setName("刘ddd刘洋洋");
        tUser.setTel("18654745d21");
        Integer rId = tMapper.insert(tUser);
        //给Mapper.xml中的insert添加useGeneratedKeys="true" keyProperty="id"这俩属性,tUser.getId()就能获取插入后的自增id
        System.out.println(tUser.toString());
        System.out.println(rId);
    } 
}

可以看到,相关操作还是非常的简单和便捷。但实际的开发中,往往联合查询多表操作的应用场景更多。

4.2 自增主键表的操作

如果数据库中相关表的主键是自增的,如何操作?看上面的代码addUser方法中,就是操作自增主键表的样例,通过构造一个实体类,然后调用insert方法,插入。有以下几点注意事项:

(1)实体类对应自增主键的成员不用调set方法,空的就行

(2)对应的xml文件要修改insert语句,添加userGeneratedKeys="true" keyProperty="id",keyProperty指定的是对应表的主键名称。如下:

  <insert id="insert" parameterType="com.tydic.cloud_wind.models.TmpDispUser" useGeneratedKeys="true" keyProperty="id">
    insert into tmp_disp_user (id, `name`, tel
      )
    values (#{id,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR}, #{tel,jdbcType=VARCHAR}
      )
  </insert>

在完成insert操作之后,入参的实体对象,通过调用get方法就可以直接获取插入进去记录的主键id。

4.3 联合查询

前面提到,在实际的开发中,多表的联合查询以及不规则查往往比单表操作要多的多,对于我而言,我更喜欢使用Map作为查询的结果,下面记录下Mybatis的自定义查询开发。网上有有一些方案是通过对实体类的构造来完成的,例如关联查询的时候,会用某个实体类所有最大类,然后进行返回,这种有利有弊,好处在于可直接操作对象,不方便之处在于代码量大,而且总要去做类的开发,参考这里

联合查询或者说自定义查询,有两种方式,一种是通过xml文件配置相关的查询sql,一种是使用注解方式。

(1) 自定义xml

即:我们可以自己编写相关xml文件,然后指定对应的Mapper接口文件。先开看一个例子:

我在dao包下新增一个CommDaoMapper接口,定义如下几个方法:

/**
 * 公用三个查询方法
 * 1.结果返回是Map<String,Object>
 * 2.结果返回是List<Map<String,Object>>
 * 3.分页查询
 */
public interface CommDaoMapper
{
    //单独参数查询,出参是Map
    Map<String,Object> selectByParams(String p1);
    //单独参数查询,出单是实体类
    TmpServerUser selectByParamsWithBean(@Param("pid") String pid);
    //Map查询参数,出参是List
    List<Map<String,Object>> selectWithList(Map<String,Object> p1);
    //独立入参,表名是动态的,出参是List
    List<Map<String,Object>> selectWithListByDt(Map<String,Object> p1);
}

然后在resources/mapper目录下新增一个CommDaoMapper.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="com.tydic.cloud_wind.dao.CommDaoMapper" >

    <!--多个独立参数查询方式-->
    <!--一个Map查询方式-->
    <!--动态指定表明查询方式-->
    <!--多表联合查询-->
    <!--分页查询-->
    <select id="selectByParams"
            resultType="java.util.Map"
            parameterType="java.lang.String" >
        select id,s_id,name,tel
        from tmp_server_user
        where id = #{0}
    </select>
    <select id="selectByParamsWithBean"
            resultType="com.tydic.cloud_wind.models.TmpServerUser"
            parameterType="java.lang.String" >
        select *
        from tmp_server_user
        where id = #{pid}
    </select>
    <select id="selectWithList"
            resultType="java.util.Map"
            parameterType="java.util.Map" >
        select *
        from tmp_disp_user a,tmp_server_user b
        where a.id=b.id and a.name like '%' || #{u_name} || '%'
    </select>
    <select id="selectWithListByDt"
            resultType="java.util.Map"
            parameterType="java.util.Map" >
        select *
        from ${t_name} a
        where a.name like '%' || #{u_name} || '%'
    </select>
</mapper>

然后在MybatisService中写一个测试方法如下:

    @Autowired
    private CommDaoMapper       cMapper;
    //自定义的查询
    public void selectWithDsign()
    {
        //selectByParams
        Map<String,Object> rMap = cMapper.selectByParams("2");
        System.out.println(rMap.toString());
        //selectByParamsWithBean
        TmpServerUser tUser = cMapper.selectByParamsWithBean("2");
        System.out.println(tUser.toString());
        //selectWithList
        Map<String,Object> p1 = new HashMap<>();
        p1.put("u_name","洋");
        List<Map<String,Object>> rList = cMapper.selectWithList(p1);
        System.out.println(rList.toString());
        //selectWithListByDt
        Map<String,Object> p2 = new HashMap<>();
        p2.put("t_name","tmp_disp_user");
        p2.put("u_name","洋");
        List<Map<String,Object>> rL2 = cMapper.selectWithListByDt(p2);
        System.out.println(rL2);
    }

这里有一个注意点,那就是在使用Map传参的时候,可以直接使用#{参数名}的方式使用参数,在直接独立传参的时候可以使用#{参数顺序的数字}方式传参。

而参数的传递,可以使用${}也可以使用#{},他们的区别在于:

#是将闯入的值当做字符串形式,很大程度上可以防止sql注入,

$是将传入的数据直接显示成sql语句,即:sql是需要动态的,参数是表名、关键字、或者order by后要使用$,(预编译前)

  #{}: 解析为一个 JDBC 预编译语句(prepared statement)的参数标记符,一个 #{ } 被解析为一个参数占位符 。

   ${}: 仅仅为一个纯碎的 string 替换,在动态 SQL 解析阶段将会进行变量替换。

(2)注解方式

个人感觉注解方式更简单一些,不用配置xml文件,当然,不配置xml文件有些mybatis的高级特性可能也没法使用,各有利弊吧,样例如下:

public interface CommDaoMapper
{
    //--通过注解方式进行数据库的操作,不用对应的xml文件
    @Select("select * from tmp_disp_user")
    List<TmpDispUser> findAllTmpUser();

    @Select("select * from tmp_disp_user where id= #{id}")
    TmpDispUser findTmpUserById(@Param("id") int id);

    @Update("update tmp_disp_user set name=#{name} where id=#{id}")
    int updateTmpUser(TmpDispUser tUser);

    @Select("select * from tmp_disp_user a,tmp_server_user b where a.id=b.id and a.name like '%' || #{u_name} || '%'")
    List<Map<String,Object>> findWithList(Map<String,Object> p1);

    @Select("select * from ${t_name} a where a.name like '%' || #{u_name} || '%'")
    List<Map<String,Object>> findWithListByDt(Map<String,Object> p1);
}

这样就行了,很简单。

4.4 分页查询

MyBatis的分页查询推荐使用pagehelper插件来完成,这个插件非常的好用,可以无侵入的完成分页,不用修改原来全量查询的sql语句和xml文件。只需要在使用的时候设置下分页相关信息即可。具体参考这里

(1)pom.xml中增加依赖

<dependency>
	<groupId>com.github.pagehelper</groupId>
	<artifactId>pagehelper-spring-boot-starter</artifactId>
	<version>1.2.7</version>
</dependency>

(2)编写自动配置类

@Configuration//将该类加到spring容器里
public class PageHelperConfig {
    @Bean//加上该注解spring容器自动配置
    public PageHelper pageHelper() {
        PageHelper pageHelper = new PageHelper();
        Properties properties = new Properties();
        properties.setProperty("offsetAsPageNum", "true");
        properties.setProperty("rowBoundsWithCount", "true");
        properties.setProperty("reasonable", "true");
        properties.setProperty("dialect","mysql");
        pageHelper.setProperties(properties);
        return pageHelper;
    }
}

(3)使用样例

使用的时候非常的方便,只需要在调用原来正常查询之前添加startPage方法即可,如下所示:

public void testFY1()
{
     //xml单表分页查询
        PageHelper.startPage(1,5);
        List<QeUserAsk> rList = qAsk.selectAll();
        PageInfo<SysUserInfo> page = new PageInfo<>(rList);
        System.out.println("总数量:" + page.getTotal());
        System.out.println("当前页查询记录:" + page.getList().size());
        System.out.println("当前页码:" + page.getPageNum());
        System.out.println("每页显示数量:" + page.getPageSize());
        System.out.println("总页:" + page.getPages());
        System.out.println(rList);
}

如上就是查询第一页,每页5条数据。而selectAll是查询全量数据的sql。所以可以说是无侵入的分页插件,很方便。

注意:一个方法中只能使用一次startPage和对应的一次查询,不能查询多次。

5.关于事物

SpringBoot的事物管理以及传播网上有很多非常优秀的博文,这里贴几个比较好的。我主要记录下我在学习过程中对遇到的一些点和认识。

【事务相关】SpringBoot之数据库(四)——事务处理:隔离级别与传播行为

【事务相关】原理剖析

【事务相关】数据库-事务和锁

【事务相关】SpringBoot之事务处理机制

【事务相关】SpringBoot 数据库事务7种传播行为

【事务相关】spring事务@Transactional在同一个类中的方法调用不生效

5.1 关于@EnableTransactionManagement注解

网上不少博文都提到,要使用事物,需要先通过@EnableTransactionManagement注解开启事物,然后再对应的类上使用@Transactional注解。其实是@EnableTransactionManagement并不会必须的。SpringBoot会开启自动配置的事物管理器,如果你使用JDBC或者JPA作为数据库访问技术,就会自动开启@EnableTransactionManagement这个注解,所以不用再显示的调用了。只需要在进行事物管理的类上添加@Transactional注解即可。

5.2 关于@Transactional注解的位置

注解最好是放置在类上,而不是方法上,如果放到方法上,有可能会有一些bug的存在,例如:
A类中方法1和方法2,给方法1添加事物注解,方法2不添加,但是方法2中会调用方法1,这之后我们测试,如果出问题了,
事物是不会回滚的。因为我们在调用方法2的时候,方法2并没有被拦截,而方法2中调用方法1属于类内部调用,也不会触动方法拦截,所以事物失效。

5.3 关于try/catch

不要在service中想进行事物控制的方法中进行try/catch,因为如果你自己拦截了异常,事物拦截器就无法正常工作了,进而事务也就失效了,最好的办法是把异常抛出去,在控制层处理,或者通过切面全局处理。

5.4 关于事物传播

SpringBoot有7中传播行为,默认是REQUIRED(0),即:如果当前存在事物,就沿用当前事物,否则新建一个事物运行子方法,在方法嵌套执行过程中,只要有一个出错,全部都会回退。

常用的还有REQUIRES_NEW(3),无论当前事务是否存在,都会创建新事务运行方法,新事务可以拥有新的锁和隔离级别等特性,与当前事务相互独立。谁出错了,谁回退。

还有NESTED(6),在当前方法调用子方法时,如果子方法发生异常,只回滚子方法执行过的SQL,而不回滚当前方法的事务。

6.druid监控

写一个自动配置类即可,代码如下:

package com.tydic.cloud_wind.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

/**
 * @Author: Zhouc52
 * @Description:
 * @Date: Create in 10:40 2019/4/19
 * @Modified by:
 * 项目正常启动后,浏览器输入:
 * http://127.0.0.1:8080/druid  输入用户名密码即可访问
 */
@Configuration
public class DruidConfig
{
    @Bean public ServletRegistrationBean druidServlet()
    {
        // 主要实现WEB监控的配置处理
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*"); // 现在要进行druid监控的配置处理操作
        servletRegistrationBean.addInitParameter("allow", "127.0.0.1,192.168.1.116"); // 白名单
        servletRegistrationBean.addInitParameter("deny", "192.168.1.200"); // 黑名单
        servletRegistrationBean.addInitParameter("loginUsername", "admin"); // 用户名
        servletRegistrationBean.addInitParameter("loginPassword", "admin"); // 密码
        servletRegistrationBean.addInitParameter("resetEnable", "false"); // 是否可以重置数据源
        return servletRegistrationBean;
    }

    @Bean public FilterRegistrationBean filterRegistrationBean()
    {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new WebStatFilter());
        filterRegistrationBean.addUrlPatterns("/*"); // 所有请求进行监控处理
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.css,/druid/*");
        return filterRegistrationBean;
    }

    /**
        这里@Bean如果不带参数,在进入druid的监控页的时候,会看到数据源未设置的错误提示,
        但是前台操作请求过一次数据库,这个错误就消失了,存在这样的问题在于mybatis可以独立SpringBoot
        使用,是需要指定数据源关闭方法以及初始化方法的,否则druid首次会获取数据源失败,待Spring有请求
        之后,druid从SpringBoot获取到SpringBoot接管的数据源,所以就显示正常了
     */

    @Bean(destroyMethod = "close",initMethod = "init")
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druidDataSource()
    {
        return new DruidDataSource();
    }
}

项目启动之后,直接在浏览器中输入:

http://127.0.0.1:8080/druid

输入用户名密码,即可访问,这个监控非常强大,截图如下:

其中druidDataSource设置@Bean注解的时候,要添加参数,否则你会发现首次进入点击数据源,会有以下错误:

注:
有些时候可能会出现SQL监控中无数据,看不到sql查询的监控的情况。主要问题在于,首先要在application.properties中添加:

spring.datasource.filters=stat,wall,log4j

然后如果没有引入log4j的依赖,要在pom.xml中引入log4j的依赖:

<dependency>
     <groupId>org.slf4j</groupId>
     <artifactId>slf4j-log4j12</artifactId>
</dependency>

然后再查看,可以正常监控sql了。

7.Controller

控制器通过@RestController注解来声明,这里我主要写了几个样例,因为控制器中主要涉及的就是接口的入参和出参。其中出参的话SpringBoot会自动帮我们转换成JSON对象。入参相关样例如下:

package com.tydic.cloud_wind.controller;

import com.tydic.cloud_wind.models.TestLable;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 * 入参的几种情况
 * 1.使用HttpServletRequest req获取参数
 * 2.使用@RequestParam注解获取参数
 * 3.使用@RequestBody获取参数
 */

@RestController
@RequestMapping("/index")
public class IndexController
{
    /**
     * 前端请求时候Content-type=application/x-www-form-urlencoded;charset=UTF-8
     * 可以使用post或者get,入参要有参数名p1,p2
     * 可以是提交表单、也可以是普通的ajax提交
     * 如果使用axios,需要使用QS.stringify(params)规格化
     * params:
     * {
     *     "p1":"1",
     *     "p2":"2"
     * }
     * 注:HttpServletRequest req可以放到下面每个方法中带到service层去使用
     */
    @RequestMapping(value = "/t1",method = {RequestMethod.GET,RequestMethod.POST})
    public Object test1(HttpServletRequest req)
    {
        String p1 = req.getParameter("p1");
        String p2 = req.getParameter("p2");
        Map<String,Object> rMap=new HashMap<>();
        rMap.put("code","0");
        rMap.put("des","成功!");
        rMap.put("result","参数1="+p1+",参数2="+p2);
        return rMap;
    }

    /**
     * 同t1规则一样,需要说明的是,看到有些文章说@RequestParam无法接收到POST请求的参数,其实并不是
     * 只要Content-type=application/x-www-form-urlencoded;charset=UTF-8,都是可以的.默认参数的required都是必填,可以设置为false,调整为非必填。如果入参的参数名和这里参数名一致,也可以省略掉@RequestParam的注解。
     */
    @RequestMapping(value = "/t2",method = {RequestMethod.GET,RequestMethod.POST})
    public Object test2(@RequestParam(defaultValue = "") String p1,@RequestParam(required = false) String p2)
    {
        Map<String,Object> rMap=new HashMap<>();
        rMap.put("code","0");
        rMap.put("des","成功!");
        rMap.put("result","参数1="+p1+",参数2="+p2);
        return rMap;
    }

    /**
     * 前端请求的时候设置Content-type=application/json
     * 只支持post请求
     * 如果使用axios提交,则直接提交即可,一定不能再使用QS.stringify(params)规格化,
     * 不然后端收不到参数,这种方式,SpringBoot会自动把入参转换成对象
     */
    @PostMapping(value = "/t3")
    public Object test3(@RequestBody Map<String,Object> p1)
    {
        System.out.println(p1.toString());
        Map<String,Object> rMap=new HashMap<>();
        rMap.put("code","0");
        rMap.put("des","成功!");
        rMap.put("result",p1);
        return rMap;
    }

    /**
     * 同test3方法,不同之处在于,也可以指定Content-type=text/plain或者其他格式
     * 收到的入参String是什么就是什么,不会再做特殊处理。
     */
    @PostMapping(value="/t4")
    public Object test4(@RequestBody String p1)
    {
        System.out.println(p1);
        Map<String,Object> rMap=new HashMap<>();
        rMap.put("code","0");
        rMap.put("des","成功!");
        rMap.put("result",p1);
        return rMap;
    }

    /**
     * 同test3,不同之处在于,要求入参的json格式必须和实体类对的上,否则获取不到入参参数
     * 关于这种,假设前端入参的参数名和后端对象的成员名称一致,这里可以省略掉@RequestBody,同时前端请求的时候,也可以test1那样的请求,接口这边Spring会自动处理。
     */
    @PostMapping(value="/t5")
    public Object test5(@RequestBody TestLable p1)
    {
        System.out.println(p1.toString());
        Map<String,Object> rMap=new HashMap<>();
        rMap.put("code","0");
        rMap.put("result",p1);
        return rMap;
    }

    /**
     * url上带参数的形式,可以是post,可以是get。一般情况下Content-Type是application/json,TestLable p1就是放到body里入参。
     */
    @RequestMapping(value="/t6/{up1}/{up2}")
    public Object test5(@PathVariable String up1,@PathVariable String up2,@RequestBody TestLable p1)
    {
        System.out.println(p1.toString());
        Map<String,Object> rMap=new HashMap<>();
        rMap.put("code","0");
        rMap.put("result",p1);
        return rMap;
    }
}

注:

8.Swagger

Swagger绝对是神器,在使用Swagger之前,接口的描述都需要通过word文档进行定义和描述,要写清楚出参和入参,对于接口对接方,还需要使用各种工具进行接口的测试,
但是项目集成Swagger之后,这一切的一切都可以省掉了,Swagger是一个可视化的接口文档描述插件,只需要接口开发人员在接口代码上添加相关的注解说明,接口调用方
就可以通过界面化的平台看到所有的接口说明,并且平台还提供的在线测试功能,简直爽的无以复加。下面记录下项目中如何引入Swagger以及如何使用。

8.1 添加maven依赖

<!-- Swagger2 -->
<dependency>
	<groupId>io.springfox</groupId>
	<artifactId>springfox-swagger2</artifactId>
	<version>2.4.0</version>
</dependency>
<dependency>
	<groupId>io.springfox</groupId>
	<artifactId>springfox-swagger-ui</artifactId>
	<version>2.4.0</version>
</dependency>

8.2 添加自动配置类

@Configuration
@EnableSwagger2
public class Swagger2
{
    @Bean
    public Docket createRestApi(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.tydic.cloud_wind"))
                .paths(PathSelectors.any())
                .build();
    }
    //访问url的端口号和服务的端口号一样,只不过后面多一个/swagger-ui.html
    private ApiInfo apiInfo(){
        return new ApiInfoBuilder()
                .title("Spring Boot基础框架集成Swagger2")
                .description("使用Swagger2测试接口及接口说明查看")
                .termsOfServiceUrl("http://localhost:8080/swagger-ui.html")
                .contact(new Contact("zhouc52","http://sina.com","49763652@qq.com"))
                .version("1.0")
                .build();
    }
}

注意basePackage的设置,一定要设置正确,不然看不到相关接口说明。

8.3 给接口配置说明

@RestController
@RequestMapping("/index")
@Api(value = "indexController控制器相关api" , description = "这里是描述信息,可以自己填写")
public class IndexController
{
    /**
     * 前端请求时候Content-type=application/x-www-form-urlencoded;charset=UTF-8
     * 可以使用post或者get,入参要有参数名p1,p2
     * 可以是提交表单、也可以是普通的ajax提交
     * 如果使用axios,需要使用QS.stringify(params)规格化
     * params:
     * {
     *     "p1":"1",
     *     "p2":"2"
     * }
     *
     */
    @ApiOperation(value="测试接口1",notes = "测试接口1,入参通过HttpServletRequest req方式获取")
    @ApiImplicitParams({
            @ApiImplicitParam(name="p1",value = "参数1",dataType = "String",paramType = "query",required = true),
            @ApiImplicitParam(name="p2",value = "参数2",dataType = "String",paramType = "query")
    })
    @RequestMapping(value = "/t1",method = {RequestMethod.GET,RequestMethod.POST})
    public Object test1(HttpServletRequest req)
    {
        String p1 = req.getParameter("p1");
        String p2 = req.getParameter("p2");
        Map<String,Object> rMap=new HashMap<>();
        rMap.put("code","0");
        rMap.put("des","成功!");
        rMap.put("result","参数1="+p1+",参数2="+p2);
        return rMap;
    }

    /**
     * 同t1规则一样,需要说明的是,看到有些文章说@RequestParam无法接收到POST请求的参数,其实并不是
     * 只要Content-type=application/x-www-form-urlencoded;charset=UTF-8,都是可以的
     */
    @ApiOperation(value="测试接口2",notes = "测试接口2,入参通过@RequestParam方式获取")
    @ApiImplicitParams({
            @ApiImplicitParam(name="p1",value = "参数1",dataType = "String",paramType = "query",required = true),
            @ApiImplicitParam(name="p2",value = "参数2",dataType = "String",paramType = "query")
    })
    @RequestMapping(value = "/t2",method = {RequestMethod.GET,RequestMethod.POST})
    public Object test2(@RequestParam String p1,@RequestParam String p2)
    {
        Map<String,Object> rMap=new HashMap<>();
        rMap.put("code","0");
        rMap.put("des","成功!");
        rMap.put("result","参数1="+p1+",参数2="+p2);
        return rMap;
    }

    /**
     * 前端请求的时候设置Content-type=application/json
     * 只支持post请求
     * 如果使用axios提交,则直接提交即可,一定不能再使用QS.stringify(params)规格化,
     * 不然后端收不到参数,这种方式,SpringBoot会自动把入参转换成对象
     */
    @ApiOperation(value="测试接口3",notes = "测试接口3,入参通过@RequestBody获取对象")
    @ApiImplicitParam(name = "p1",value = "查询的Map对象")
    @PostMapping(value = "/t3")
    public Object test3(@RequestBody Map<String,Object> p1)
    {
        System.out.println(p1.toString());
        Map<String,Object> rMap=new HashMap<>();
        rMap.put("code","0");
        rMap.put("des","成功!");
        rMap.put("result",p1);
        return rMap;
    }

    /**
     * 同test3方法,不同之处在于,也可以指定Content-type=text/plain或者其他格式
     * 收到的入参String是什么就是什么,不会再做特殊处理。
     */
    @PostMapping(value="/t4")
    public Object test4(@RequestBody String p1)
    {
        System.out.println(p1);
        Map<String,Object> rMap=new HashMap<>();
        rMap.put("code","0");
        rMap.put("des","成功!");
        rMap.put("result",p1);
        return rMap;
    }

    /**
     * 同test3,不同之处在于,要求入参的json格式必须和实体类对的上,否则获取不到入参参数
     *
     */
    @ApiOperation(value="测试接口5",notes = "测试接口5,入参是实体类通过@RequestBody获取对象")
    @ApiImplicitParam(name = "p1",value = "实体类",dataType = "TestLable")
    @PostMapping(value="/t5")
    public Object test5(@RequestBody TestLable p1)
    {
        System.out.println(p1.toString());
        Map<String,Object> rMap=new HashMap<>();
        rMap.put("code","0");
        rMap.put("result",p1);
        return rMap;
    }
}

8.4 界面化接口说明文档

在浏览器中输入:http://localhost:8080/swagger-ui.html

9.关于restful

Restful api是一种规范,不是什么新技术。我对restful的简单理解就是通过url可以直接看出请求是什么资源,简而言之就是以前强调动作,现在强调资源,
然后通过使用http的不同请求标识不同动作,例如:get是查询,post是提交修改、delete是删除、put是修改等等。然后url的末尾是资源标识。
但现实中,这种方式并不是很理想,例如:我想批量删除怎么办?我的一个动作要更新多个订单的状态,此时怎么设计?等等。
其实,我们平常用的http post请求也是restful api,只不过不规范而已,但是,满足需求,并能快速解决问题就是好的方案。
这篇博文说的也很好,可以参考下。

10.关于全局异常捕获

为了避免服务直接报异常,对服务调用方产生不好的体验,可以添加一个全局异常捕获类,对于运行过程中由控制器抛出的异常,我们都可以拦截到,并给出统一的提示,这样在一定程度上增加友好度。当然,最好还是后台开发人员在写代码的时候能自己对自己的代码负责,处理好相关异常逻辑。
增加全局异常拦截的非常简单,写一个配置类即可,如下所示:
 

@ControllerAdvice
public class GlobalExceptionHandler
{
    @ExceptionHandler({RuntimeException.class})
    @ResponseBody
    public ResultJson exceptionHandler(RuntimeException e,HttpServletResponse resp)
    {
        ResultJson rJson = new ResultJson();
        rJson.setCode(CONST.FAILE);
        rJson.setDescription("很遗憾,服务器开小差了,请稍后再试!");
        rJson.setResult(e.getMessage());
        return rJson;
    }

    @ExceptionHandler({HttpRequestMethodNotSupportedException.class})
    @ResponseBody
    public ResultJson supportedException(HttpRequestMethodNotSupportedException e,HttpServletResponse resp)
    {
        ResultJson rJson = new ResultJson();
        rJson.setCode(CONST.FAILE);
        rJson.setDescription("很遗憾,服务不支持GET请求!");
        rJson.setResult(e.getMessage());
        return rJson;
    }
}

参考文献

【1】Mybatis Generator最完整配置详解

【2】SpringBoot整合MyBatis

【3】Springboot mybatis generate 自动生成实体类和Mapper

【4】SpringBoot----用MyBatis注解实现数据的增删查改

【5】Mybatis 中$与#的区别

【6】springboot整合mybatis进行分页查询

【7】SpringBoot整合Swagger2

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值