Java笔记:SpringBoot开发常用技术整合

一、构建springboot工程

参考源码地址
https://github.com/leechenxiang/imooc-springboot-starter

可选IDE
STS Spring Tool Suit

快速开始:https://spring.io/quickstart

配置文件 application.properties

############################################################
#
# 开发模式设置
#
############################################################

# 热部署生效
spring.devtools.restart.enabled=true

# 监听目录
spring.devtools.restart.additional-paths=src/main/java

#spring.devtools.restart.exclude=static/**,public/**
#spring.devtools.restart.exclude=WEB-INF/**


############################################################
#
# server 服务端配置
#
############################################################

server.port=8080

#server.servlet.context-path=/demo
#server.error.path=/error
#server.address=192.168.1.2
#server.session-timeout=60

############################################################
#
# server.tomcat 服务端配置
#
############################################################

#server.tomcat.max-threads=250
server.tomcat.uri-encoding=UTF-8
#server.tomcat.basedir=H:/springboot-tomcat-tmp
#server.tomcat.access-log-enabled=true
#server.tomcat.access-log-pattern=
#server.tomcat.accesslog.directory=
#logging.path=H:/springboot-tomcat-tmp
#logging.file=myapp.log

二、接口返回json

# 时间格式化
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
spring.jackson.serialization.write-dates-as-timestamps=false
package com.example.demo.pojo;

/**
 * 统一的返回封装
 */
public class JsonResult {
    private Integer code;
    private String msg;
    private Object data;

    public JsonResult(Integer code, String msg, Object data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public static JsonResult success(Object data){
        return new JsonResult(0, "success", data);
    }

    public static JsonResult error(String errorMessage) {
        return new JsonResult(-1, errorMessage, null);
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
}
package com.example.demo.pojo;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;

import java.util.Date;

/**
 * jackson注解使用示例
 */
@Data
public class User {

    private String name;
    private Integer age;

    // 忽略显示
    @JsonIgnore
    private String password;

    // 格式化
    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", locale="zh", timezone="GMT+8")
    private Date birthday;

    // 为空不显示
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private String desc;

}

三、热部署

<!--热部署-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>

IDEA需要做额外的配置

四、资源属性配置

resource.properties

# 配置文件
com.demo.name=MyBlog
com.demo.language=zh
package com.example.demo.pojo;


import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

/**
 * 资源文件中的配置映射到实体类
 */
@Configuration
@ConfigurationProperties(prefix = "com.demo")
@PropertySource(value = "classpath:resource.properties")
public class Resource {
    private String name;
    private String language;

    public String getName() {
        return name;
    }

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

    public String getLanguage() {
        return language;
    }

    public void setLanguage(String language) {
        this.language = language;
    }
}

五、模板引擎

<!--模板引擎 freemarker-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

<!--模板引擎 thymeleaf-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
############################################################
#
# freemarker
#
############################################################

# 上线改为true
spring.freemarker.cache=false
#spring.freemarker.template-loader-path=classpath:/templates
#spring.freemarker.charset=UTF-8
#spring.freemarker.check-template-location=true
#spring.freemarker.content-type=text/html
#spring.freemarker.expose-request-attributes=true
#spring.freemarker.expose-session-attributes=true
#spring.freemarker.request-context-attribute=request
#spring.freemarker.suffix=.ftl

############################################################
#
# thymeleaf
#
#############################################################
# 上线改为true
spring.thymeleaf.cache=false
#spring.thymeleaf.prefix=classpath:/templates/
#spring.thymeleaf.suffix=.html
#spring.thymeleaf.mode=HTML5
#spring.thymeleaf.encoding=UTF-8
#spring.thymeleaf.servlet.content-type=text/html

# 静态文件
spring.mvc.static-path-pattern=/static/**

freemarker

<h2>freemarker</h2>

<p>${resource.name}</p>
<p>${resource.language}</p>

thymeleaf

基本使用方式
对象引用方式
时间类型转换
text与utext
URL
引入静态资源文件js/css
条件判断th:if与th:unless
循环th:each
分支th:switch与th:case

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script th:src="@{/static/js/index.js}"></script>
</head>
<body>

<h2>基本语法</h2>
<p><input type="text" th:value="${user.name}"  th:id="${user.name}" th:name="${user.name}"></p>
<p><input type="text" th:value="${user.age}"></p>
<p><input type="text" th:value="${user.birthday}"></p>

<h2>日期格式化</h2>
<p><input type="text" th:value="${#dates.format(user.birthday, 'yyyy-MM-dd')}"></p>

<h2>简便写法</h2>
<div th:object="${user}">
    <p><input type="text" th:value="*{name}"  th:id="*{name}" th:name="*{name}"></p>
    <p><input type="text" th:value="*{age}"></p>
    <p><input type="text" th:value="*{birthday}"></p>
</div>

<h2>text与utext</h2>
<p>text: <span th:text="${user.desc}"></span></p>
<p>utext: <span th:utext="${user.desc}"></span></p>

<h2>网址</h2>
<a th:href="@{https://www.baidu.com/}">www.baidu.com</a>

<h2>表单</h2>
<form th:action="@{/thymeleaf/user}" method="post" th:object="${user}">
    <!-- field == id, name, value -->
    <input type="text" th:field="*{name}">
    <input type="submit">
</form>

<h2>判断</h2>
<p th:if="${user.age} == 20"> age == 20</p>
<p th:if="${user.age} gt 20"> age > 20</p>
<p th:if="${user.age} lt 20"> age < 20</p>
<p th:if="${user.age} ge 20"> age >= 20</p>
<p th:if="${user.age} le 20"> age <= 20</p>

<h2>选择框</h2>
<select name="" id="">
    <option th:selected="${user.age} == 20">20</option>
    <option th:selected="${user.age} == 18">18</option>
</select>

<h2>循环</h2>
<p th:each="person:${userList}">
    <span th:text="${person.name}"></span>
</p>

<h2>分支</h2>
<p th:switch="${user.name}">
    <span th:case="#{roles.superadmin}">超级管理员</span>
    <span th:case="#{roles.manager}">管理员</span>
    <span th:case="*">普通会员</span>
</p>
</body>
</html>

六、异常处理

通用异常 web和ajax

package com.example.demo.exception;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 处理错误页面
 */
@ControllerAdvice
public class CustomExceptionHandler {
    public static final String ERROR_VIEW = "thymeleaf/error";

    @ExceptionHandler(value = Exception.class)
    public Object errorHandler(HttpServletRequest request,
                               HttpServletResponse response, Exception e) throws Exception {

        if (isAjax(request)) {
            ModelAndView model = new ModelAndView(new MappingJackson2JsonView());
            model.addObject("data", null);
            model.addObject("code", -1);
            model.addObject("msg", e.getMessage());
            return model;
        } else {
            ModelAndView model = new ModelAndView();
            model.addObject("exception", e);
            model.addObject("url", request.getRequestURL());
            model.setViewName(ERROR_VIEW);

            return model;
        }

    }

    /**
     * 判断是否为ajax
     *
     * @param request
     * @return
     */
    public boolean isAjax(HttpServletRequest request) {
        String ContentType = request.getHeader("Content-Type");
        String Accept = request.getHeader("Accept");

        if (ContentType != null &&
                ContentType.contains("json")) {
            return true;
        } else if (Accept != null &&
                Accept.contains("json")) {
            return true;
        } else {
            return false;
        }
    }


}

处理ajax错误

package com.example.demo.exception;

import com.example.demo.pojo.JsonResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 处理ajax错误
 */
@RestControllerAdvice
public class AjaxExceptionHandler {

    @ExceptionHandler(value = Exception.class)
    public JsonResult errorHandler(HttpServletRequest request,
                               HttpServletResponse response, Exception e) throws Exception {

        return JsonResult.error(e.getMessage());
    }
}

七、MyBatis

依赖引入pom.xml

<!--数据库相关-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.20</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.0</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.9</version>
</dependency>

<!--mybatis-->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.1</version>
</dependency>

<!--mapper-->
<!--版本过低会报错-->
<!--tk.mybatis.mapper.MapperException: tk.mybatis.mapper.provider.EmptyProvider中缺少selectOne方法!-->
<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper-spring-boot-starter</artifactId>
    <version>2.0.0</version>
</dependency>

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

<dependency>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-core</artifactId>
    <version>1.3.2</version>
    <scope>compile</scope>
    <optional>true</optional>
</dependency>

参数配置 application.properties

############################################################
#
# druid
#
############################################################
spring.datasource.url=jdbc:mysql://localhost:3306/data
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.druid.initial-size=1
spring.datasource.druid.min-idle=1
spring.datasource.druid.max-active=20
spring.datasource.druid.test-on-borrow=true
#spring.datasource.druid.stat-view-servlet.allow=true


############################################################
#
# mybatis
# https://github.com/abel533/MyBatis-Spring-Boot
############################################################
mybatis.type-aliases-package=com.example.demo.pojo
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

# 通用mapper
mapper.mappers=com.example.demo.utils.MyMapper
mapper.not-empty=false
mapper.identity=MYSQL

# 分页插件
pagehelper.helperDialect=mysql
pagehelper.reasonable=true
pagehelper.supportMethodsArguments=true
pagehelper.params=count=countSql

自动代码生成配置 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>
    <context id="MysqlContext" targetRuntime="MyBatis3Simple" defaultModelType="flat">
        <property name="beginningDelimiter" value="`"/>
        <property name="endingDelimiter" value="`"/>

        <plugin type="tk.mybatis.mapper.generator.MapperPlugin">
            <property name="mappers" value="com.example.demo.utils.MyMapper"/>
        </plugin>

        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/data"
                        userId="root"
                        password="123456">
        </jdbcConnection>

        <!-- 对于生成的pojo所在包 -->
        <javaModelGenerator targetPackage="com.example.demo.pojo" targetProject="src/main/java"/>

        <!-- 对于生成的mapper所在目录 -->
        <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources"/>

        <!-- 配置mapper对应的java映射 -->
        <javaClientGenerator targetPackage="com.example.demo.mapper" targetProject="src/main/java"
                             type="XMLMAPPER"/>


        <table tableName="sys_user"></table>

    </context>
</generatorConfiguration>

逆向工程工具

package com.example.demo.utils;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.internal.DefaultShellCallback;

public class GeneratorDisplay {

    public void generator() throws Exception{

        List<String> warnings = new ArrayList<String>();
        boolean overwrite = true;
        // 指定 逆向工程配置文件
        File configFile = new File("generatorConfig.xml");
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(configFile);
        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,
                callback, warnings);
        myBatisGenerator.generate(null);

    } 

    public static void main(String[] args) throws Exception {
        try {
            GeneratorDisplay generatorSqlmap = new GeneratorDisplay();
            generatorSqlmap.generator();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

通用Mapper

package com.example.demo.utils;

import tk.mybatis.mapper.common.Mapper;
import tk.mybatis.mapper.common.MySqlMapper;

/**
 * 继承自己的MyMapper
 */
public interface MyMapper<T> extends Mapper<T>, MySqlMapper<T> {
    //TODO
    //FIXME 特别注意,该接口不能被扫描到,否则会出错
}
package com.example.demo.mapper;

import com.example.demo.pojo.SysUser;
import com.example.demo.utils.MyMapper;

public interface UserMapper extends MyMapper<SysUser> {
}

自定义Mapper

package com.example.demo.mapper;

import com.example.demo.pojo.Person;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

// 自定义Mapper
public interface PersonMapper {
    Person queryUserById(Integer id);

    @Transactional(propagation= Propagation.REQUIRED)
    void deleteById(Integer id);

    @Transactional(propagation= Propagation.REQUIRED)
    void updateById(Person person);
}
package com.example.demo.pojo;

import lombok.Data;

@Data
public class Person {

    private Integer id;
    private String name;
    private Integer age;
}
<?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.example.demo.mapper.PersonMapper" >

    <select id="queryUserById" resultType="com.example.demo.pojo.Person" parameterType="java.lang.Integer">
    select
        id, name, age
    from
        person
    where
        id = #{id, jdbcType=INTEGER}
    limit 1

 </select>

    <update id="updateById">
        update person
        set name = #{name}, age = #{age}
        where id = #{id}
    </update>

    <delete id="deleteById">
        delete from person
        where id = #{id}
    </delete>
</mapper>

mybatis
generatorConfig生成mapper和pojo
实现CURD
mybatis-pagehelper实现分页
自定义mapper实现
xml形式的sql形式有利于后期调优
使用包含pagehelper分页的MyBatis的开源框架:
https://github.com/abel533/MyBatis-Spring-Boot

事务:
事务隔离级别
default
read_uncommitted
read_committed
repeatable_read
serializable

事务传播行为
required 有事务直接用,没有新建事务
supports 有事务直接用,没有也可以
mandatory
requires_new
not_supported
never
nested

使用场景

@Transactional(propagation=Propagation.SUPPORTS)查询

@Transactional(propagation=Propagation.REQUIRED)增加删除修改

解决devtools与Mapper冲突

添加配置文件 META-INF/spring-devtools.properties

restart.include.hifi=/hifi-[\\w-\\.\\d]+.jar
restart.include.mybatis=/mybatis-[\\w-\\.\\d]+.jar
restart.include.mapper=/mapper-[\\w-\\.\\d]+jar
restart.include.pagehelper=/pagehelper-[\\w-\\.\\d]+jar

八、redis

引入依赖

<!-- redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置

############################################################
#
# REDIS
#
############################################################

spring.redis.database=1
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
spring.redis.pool.max-active=1000
spring.redis.pool.max-wait=-1
spring.redis.pool.max-idle=10
spring.redis.pool.min-idle=2
spring.redis.timeout=0

使用

package com.example.demo.controller;

import com.example.demo.pojo.JsonResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
public class RedisController {
    @Autowired
    private StringRedisTemplate redisTemplate;

    @GetMapping("/redis")
    public JsonResult error() {
        redisTemplate.opsForValue().set("name", "Tom");

        return JsonResult.success(redisTemplate.opsForValue().get("name"));
    }


}

九、定时任务

@EnableScheduling // 开启定时任务
package com.example.demo.task;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;

@Component
public class HelloTask {
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
    // 每3秒执行一次
    @Scheduled(fixedRate = 3000)
    public void echo(){
        System.out.println("echo: " + dateFormat.format(new Date()));
    }


}

十、异步任务

@EnableAsync // 开启异步任务
package com.example.demo.task;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Component
public class HelloTask {

    @Async
    public void sayHello() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

十一、拦截器

定义拦截器

package com.example.demo.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class HelloInterceptor implements HandlerInterceptor {

    // 请求处理之前
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle");

        if("Tom".equals(request.getParameter("key"))){
            System.out.println("被拦截");
            return false;
        } else{
            System.out.println("放行");
            return true;
        }
    }

    // 请求处理之后
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion");
    }
}

注册拦截器

package com.example.demo.config;

import com.example.demo.interceptor.HelloInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

//WebMvcConfigurerAdapter已经被废弃了
@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {
    // 拦截器按照顺序执行
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new HelloInterceptor()).addPathPatterns("/**");
    }
}