一、工程结构规范
![]() | 1、数据持久层:底层 MySQL 、 Oracle 、 Hbase 等进行数据交互 2、各个层级有自己的数据封装实体类:DO、DTO、VO、query等等 3、dao层引入数据库信息,除了实体类,还包含mapper 4、从jar引入角度看 manager引入dao,service引入manager web(controller层)引入service 对外接口api层引入service,但是建议不引入controller层 5、从调用关系看,就是如图的调用层级关系 6、common通用包 应该由dao层引入,这样各个模块都可以使用,里面包含各种工具类、日志、lombok等通用的东西 |
---|
二、创建springBoot项目
1、Idea中,我们可以直接使用Maven创建
如果是gitLab或者其他托管网站新建的项目,是空的,我们clone后,需要自己创建一个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>
<!--组织GAV坐标-->
<groupId>com.rj</groupId>
<!--项目名称-->
<artifactId>bootest2</artifactId>
<!--项目版本-->
<version>1.0.0-SNAPSHOT</version>
<!--打包格式,pom表示有子模块,该pom为父模块,一般maven默认是jar包-->
<packaging>pom</packaging>
</project>
然后,点击IDEA右边边栏的maven图标,点击+号,将这个pom文件导入即可。
2、在大的外层模块中继续创建各种子模块
![]() | ![]() |
---|
后记:在IDEA工程中,右下角是否为UTF-8
,不是的话需要在settings->encoding搜索修改,然后在File->other settings->encoding搜索修改,这样保证其他项目新建的时候也是utf-8
一般这样就可以了,另外就是注意,如果还是报错,而且是xml文件解析报错,注意如下修改:<?xml version="1.0" encoding="UTF-8" ?>
改为<?xml version="1.0" encoding="UTF8" ?>
3、根据工程结构规范,进行包引入
<!--这是service层,由于没有manager,则需要直接引入common和dao-->
<dependencies>
<dependency>
<groupId>com.rj</groupId>
<artifactId>common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.rj</groupId>
<artifactId>dao</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<!--这是api层-->
<!--可以将项目中其他模块jar包引入,版本号与当前模块的parent一致-->
<dependency>
<groupId>com.rj</groupId>
<artifactId>service</artifactId>
<version>${project.version}</version>
</dependency>
4、引入springBoot依赖,进行版本控制
- 1、版本的控制必须在父模块的pom文件中控制,即
SpringBootTest
模块
<!--版本号控制-->
<properties>
<spring-boot-parent.version>2.0.1.RELEASE</spring-boot-parent.version>
<spring-boot-mybatis.version>2.0.0</spring-boot-mybatis.version>
<mybatis-plus-boot-starter>3.4.2</mybatis-plus-boot-starter>
<mybatis-plus-generator>3.4.0</mybatis-plus-generator>
<hutool-all.version>5.6.6</hutool-all.version>
<spring-boot-druid.version>1.1.10</spring-boot-druid.version>
<mysql.version>5.1.29</mysql.version>
<lombok.version>1.16.10</lombok.version>
<junit4.version>4.12</junit4.version>
<java.version>1.8</java.version>
<maven-source-plugin.version>2.1.2</maven-source-plugin.version>
<maven-compiler-plugin.version>2.3.2</maven-compiler-plugin.version>
<maven-deploy-plugin.version>2.7</maven-deploy-plugin.version>
<maven-surefire-plugin.version>2.6</maven-surefire-plugin.version>
<jackson-version>2.9.5</jackson-version>
</properties>
<!--这个是版本控制管理,不会引入jar包,而只是声明名称和版本,具体的引用在子模块-->
<dependencyManagement>
<dependencies>
<!--引入需要的springBoot组件,需要指定版本号${version}-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot-parent.version}</version>
</dependency>
<!--log日志,内置的是logback-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<version>${spring-boot-parent.version}</version>
</dependency>
<!--Mybatis+baomidou+framemarker-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${spring-boot-mybatis.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus-boot-starter}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
<version>${spring-boot-parent.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>${mybatis-plus-generator}</version>
</dependency>
<!--Mybatis+baomidou+framemarker-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring-boot-parent.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${spring-boot-druid.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool-all.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit4.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson-version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson-version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson-version}</version>
</dependency>
</dependencies>
</dependencyManagement>
使用
dependencyManagement
和直接使用dependencies
的最大区别,在于,前者只是声明要使用什么依赖以及对应版本号,但不会真正导入依赖,即使飘红也没关系;后者会真正导入依赖。
所以,使用前者,我们就可以在子模块中根据子模块的实际情况进行实际的jar包引入;而后者,则会在各个子模块中都会全部引入,这是很不合规范的
- 2、在子模块中根据实际情况真实引入jar包依赖
<!--这是api层,我们只需要引入spring-boot-starter-web的jar包-->
<dependencies>
<!--子模块根据需要实际引入jar包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
![]() | 同理,可以在dao层引入数据库、mybatis、mybatis-plus等对应的jar包 在common可以引入日志依赖,根据传递依赖原则,在顶层的api层都有依赖进来。 |
---|
5、配置文件
在web或api层的resource目录中配置yml文件,注意命名规范:application.yml
#dataSource
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
url: jdbc:mysql://localhost:3306/mydb3?useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
druid:
initial-size: 3
min-idle: 3
max-active: 10
max-wait: 60000
filter:
stat:
log-slow-sql: true
slow-sql-millis: 1
#环境:env就是pom中的环境选择标签
profiles:
active: ${env}
#tomcat端口号
server:
port: 8089
servlet:
context-path: /rj
tomcat:
uri-encoding: utf-8
#mybatis
mybatis-plus:
type-aliases-package: com.rj.domain
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:com/rj/*/mapper/*.xml
#log日志配置
logging:
config: classpath:logback.xml
同级位置
配置对应的开发或者生成环境配置文件,注意明明规范application-dev.yml
和application-pro.yml
并在父pom
文件中配置环境选择
<!-- 开发环境,默认激活 -->
<profiles>
<profile>
<!--id要和env中的一致-->
<id>dev</id>
<properties>
<!--这个只是针对的文件后缀,即application-后面的-->
<env>dev</env>
</properties>
<!--默认环境 -->
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<!-- 生产环境 -->
<profile>
<id>pro</id>
<properties>
<env>pro</env>
</properties>
</profile>
</profiles>
同级位置
引入我们的日志配置logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 日志路径 -->
<property name="log_dir" value="/export/Logs/iceStatistic"/>
<!-- 控制台 -->
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern><!--%black(控制台-)--> %red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{10}) - %cyan(%msg%n)
</pattern>
</encoder>
</appender>
<!-- debug日志 -->
<appender name="debug" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log_dir}/debug.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log_dir}/debug-%d{yyyy-MM-dd-HH}.log</fileNamePattern>
<maxHistory>48</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{HH:mm:ss.SSS} %thread %X{invokeNo} %logger{40} %msg%n</pattern>
</encoder>
</appender>
<!-- info日志 -->
<appender name="info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log_dir}/info.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log_dir}/info-%d{yyyy-MM-dd-HH}.log</fileNamePattern>
<maxHistory>72</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{HH:mm:ss.SSS} %thread %X{invokeNo} %logger{40} %msg%n</pattern>
</encoder>
</appender>
<!-- error日志 -->
<appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log_dir}/error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log_dir}/error-%d{yyyy-MM-dd-HH}.log</fileNamePattern>
<maxHistory>72</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{HH:mm:ss.SSS} %thread %X{invokeNo} %logger{40} %msg%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter"><!-- 只打印错误日志 -->
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<logger name="DEBUG_LOG" level="debug" additivity="false">
<appender-ref ref="debug"/>
</logger>
<logger name="INFO_LOG" level="info" additivity="false">
<appender-ref ref="info"/>
</logger>
<logger name="ERROR_LOG" level="error" additivity="false">
<appender-ref ref="error"/>
</logger>
<logger name="com.jd.flight" level="debug" additivity="false">
<appender-ref ref="stdout"/>
<appender-ref ref="debug"/>
</logger>
<root level="info">
<appender-ref ref="stdout"/>
<appender-ref ref="info"/>
<appender-ref ref="error"/>
</root>
</configuration>
6、springBoot启动类
在web或api层的java/com/rj/
下创建启动类
@SpringBootApplication
@MapperScan(value = "com.rj.*.mapper")
//@ComponentScan(value = "com.*") //扫描其他包
public class AppStart {
public static void main(String[] args) {
SpringApplication.run(AppStart.class,args);
}
}
springBootApplication是自动装配,MapperScan是对mapper文件的扫描
由于springBoot内置tomcat,直接启动这个类,就可以将项目起来
springBoot的测试类
@SpringBootTest(classes = AppStart.class)
@RunWith(SpringRunner.class)
@Slf4j
public class StudentTest {
//这个是在yml文件中配置的,虽然yml是冒号分隔,但是这里还是需要使用“.”来调用
@Value("${vipuser.name}")
private String name;
@Test
public void testStudent() {
log.error("name = {}", name);
}
}
7、在父pom中需要配置打包build和插件
<!--build-->
<!-- Package as an executable jar -->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<!--可以访问的资源文件路径-->
<includes>
<include>application.yml</include>
<include>application-${env}.yml</include>
<include>*.txt</include>
<include>**/*.xml</include>
<include>**/messages*.properties</include>
<include>**/*.csv</include>
<include>**/*.zip</include>
<include>**/*.ftl</include>
<include>*.properties</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>${maven-deploy-plugin.version}</version>
<configuration>
<uniqueVersion>false</uniqueVersion>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<failOnError>true</failOnError>
<verbose>true</verbose>
<fork>true</fork>
<compilerArgument>-nowarn</compilerArgument>
<source>${java.version}</source>
<target>${java.version}</target>
<!--必须指定编码为utf-8,否则对于有中文注释的,会出现文件解析错误-->
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- 打包时生成源码 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>${maven-source-plugin.version}</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- 跳过单元测试 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
spring-boot-maven-plugin.version
和java.version
版本也可以在父pom中控制
8、mybatis自动生成工具类1
(选用) 根据数据库表自动生成domain、mapper、mapper.xml、service和controller
//需要引入`mybatis-plus-generator`和`spring-boot-starter-freemarker`
package codegenerate;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.springframework.util.StringUtils;
import java.util.HashMap;
import java.util.Map;
/**
* 根据数据库表自动生成domain/mapper/service/controller
*/
@RunWith(JUnit4.class)
public class CodeGenerator {
/**
* 运行测试生成
*/
@Test
public void doGenerator() {
generator("user", "student");
}
/**
* 自动生成的方法
* @param nodeName 业务模块的名称,比如用户user、商品good...,同名会追加类
* @param tables 表名,可多个,建议单个生成
*/
@SuppressWarnings("all")
private static void generator(String nodeName, String... tables) {
final String fNodeName = StringUtils.isEmpty(nodeName) ? "test" : nodeName;
//代码生成器
AutoGenerator mpg = new AutoGenerator();
//局配置
GlobalConfig gc = new GlobalConfig();
//获取工程的绝对路径
final String parentPath = "D:\\Java\\ProjectJ\\springBootTest";
gc.setAuthor("rj");
gc.setOpen(false);
gc.setSwagger2(false);
mpg.setGlobalConfig(gc);
//数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/mydb3?useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true");
dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("root");
mpg.setDataSource(dsc);
//包配置: src\main\java\com\rj\domain
PackageConfig pc = new PackageConfig();
//生成文件夹
Map<String, String> configPathInfo = new HashMap(8) {
{
put(ConstVal.XML_PATH, parentPath + "\\dao\\src\\main\\java\\com\\rj\\"+fNodeName+"\\mapper");
put(ConstVal.MAPPER_PATH, parentPath + "\\dao\\src\\main\\java\\com\\rj\\"+fNodeName+"\\mapper");
put(ConstVal.ENTITY_PATH, parentPath + "\\dao\\src\\main\\java\\com\\rj\\domain\\"+fNodeName+"\\entity");
put(ConstVal.SERVICE_PATH, parentPath + "\\service\\src\\main\\java\\com\\rj\\"+fNodeName+"\\service");
put(ConstVal.SERVICE_IMPL_PATH, parentPath + "\\service\\src\\main\\java\\com\\rj\\"+fNodeName+"\\service\\impl");
put(ConstVal.CONTROLLER_PATH, parentPath + "\\api\\src\\main\\java\\com\\rj\\"+fNodeName+"\\controller");
}
};
//生成对应的类
pc.setPathInfo(configPathInfo);
pc.setParent("com.rj");
pc.setXml(fNodeName+".mapper");
pc.setMapper(fNodeName+".mapper");
pc.setEntity("domain."+fNodeName+".entity");
pc.setService(fNodeName+".service");
pc.setServiceImpl(fNodeName+".service.impl");
pc.setController(fNodeName+".controller");
mpg.setPackageInfo(pc);
//策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true);
strategy.setInclude(tables);
//表前缀,根据具体情况修改
strategy.setTablePrefix("");
mpg.setStrategy(strategy);
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();
}
}
9、mybatis自动生成工具类2
不使用baomidou的框架,避免过大的侵入性,我们可以使用org.mybatis自己的自动生成框架。
为了避免过多jar包冲突,我们直接引入原生jar
,对应的配置如下
<!--版本控制-->
<!--mybatis + mybatis-generator + pageHelper + tk.mybatis-->
<spring-boot-mybatis.version>2.0.0</spring-boot-mybatis.version>
<mybatis-generator-core.version>1.3.5</mybatis-generator-core.version>
<pagehelper.version>4.1.6</pagehelper.version>
<tk-mybatis-mapper.version>4.1.5</tk-mybatis-mapper.version>
<!--mybatis + mybatis-generator + pageHelper + tk.mybatis-->
<!--引入-->
<!--mybatis + mybatis-generator + pageHelper + tk.mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${spring-boot-mybatis.version}</version>
</dependency>
<!--根据数据库自动生成mybatis文件-->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>${mybatis-generator-core.version}</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>${pagehelper.version}</version>
</dependency>
<!--mapper继承类,可以有封装号的单表查询方法-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>${tk-mybatis-mapper.version}</version>
</dependency>
<!--mybatis + mybatis-generator + pageHelper + tk.mybatis-->
这里注意:mybatis-spring-boot-starter
中已经引入了org.mybatis
和mybatis-spring
,这里无须重新引入。
对应的yml文件也需要修改
#mybatis
mybatis:
type-aliases-package: com.rj.domain
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath*:com/rj/mapper/xml/*.xml
网上大部分都是xml+maven插件自动生成,这里我们可以使用java代码实现
@RunWith(JUnit4.class)
public class CodeGenerator {
/**
* 执行生成代码
*/
@Test
public void codeGenerator() throws InvalidConfigurationException, InterruptedException, SQLException, IOException {
List<String> warnings = new ArrayList<>();
Configuration config = getGeneratorConfig("user", "id");
DefaultShellCallback callback = new DefaultShellCallback(true);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
myBatisGenerator.generate(null);
}
/**
* 获取自动生成的配置
* @param tableName 表名
* @param indexId 主键列名称
*/
private static Configuration getGeneratorConfig(String tableName, String indexId) {
//获取工程的绝对路径
final String parentPath = "D:\\Projects\\gongyinglian\\test\\bootest2";
Context context = new Context(ModelType.CONDITIONAL);
context.setId("defaultContext");
context.setTargetRuntime("MyBatis3Simple");
/*添加属性*/
context.addProperty("javaFileEncoding", "UTF-8");
context.addProperty("beginningDelimiter","`");
context.addProperty("endingDelimiter","`");
context.addProperty("javaFormatter","org.mybatis.generator.api.dom.DefaultJavaFormatter");//格式化java代码
context.addProperty("xmlFormatter","org.mybatis.generator.api.dom.DefaultXmlFormatter");//格式化xml代码
/*
自动识别数据库关键字,默认false,如果设置为true,根据SqlReservedWords中定义的关键字列表;一般保留默认值,遇到数据库关键字(Java关键字),
使用columnOverride覆盖
*/
context.addProperty("autoDelimitKeywords","true");
//引入tk.mybatis.mapper.common.Mapper插件和属性配置
PluginConfiguration pluginConfiguration = new PluginConfiguration();
pluginConfiguration.setConfigurationType("tk.mybatis.mapper.generator.MapperPlugin");
pluginConfiguration.addProperty("mappers", "tk.mybatis.mapper.common.Mapper");
context.addPluginConfiguration(pluginConfiguration);
/*注释生成器配置*/
CommentGeneratorConfiguration commentGeneratorConfig = new CommentGeneratorConfiguration();
commentGeneratorConfig.addProperty("suppressAllComments", "true");
commentGeneratorConfig.addProperty("suppressDate","true");
context.setCommentGeneratorConfiguration(commentGeneratorConfig);
/*JDBC连接信息配置*/
JDBCConnectionConfiguration jdbcConnectionConfig = new JDBCConnectionConfiguration();
jdbcConnectionConfig.setDriverClass("com.mysql.jdbc.Driver");
//注意代码配置中JDBC连接字符串中的参数分隔符不需要再像xml配置文件中那样使用转义符
jdbcConnectionConfig.setConnectionURL("jdbc:mysql://localhost:3306/userDatabase?useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true");
jdbcConnectionConfig.setUserId("root");
jdbcConnectionConfig.setPassword("root");
jdbcConnectionConfig.addProperty("nullCatalogMeansCurrent", "true");//MySQL无法识别table标签中schema类的配置,所以在URL上指明目标数据库,并追加nullCatalogMeansCurrent属性为true
jdbcConnectionConfig.addProperty("remarksReporting", "true");//针对oracle数据库无法读取表和字段备注
jdbcConnectionConfig.addProperty("useInformationSchema", "true");//针对mysql数据库无法读取表和字段备注
context.setJdbcConnectionConfiguration(jdbcConnectionConfig);
JavaTypeResolverConfiguration javaTypeResolverConfiguration = new JavaTypeResolverConfiguration();
//是否使用bigDecimal, false可自动转化以下类型(Long, Integer, Short, etc.)
javaTypeResolverConfiguration.addProperty("forceBigDecimals","false");
context.setJavaTypeResolverConfiguration(javaTypeResolverConfiguration);
/*domain生成器配置*/
JavaModelGeneratorConfiguration javaModelGeneratorConfig = new JavaModelGeneratorConfiguration();
javaModelGeneratorConfig.setTargetProject(parentPath + "\\dao\\src\\main\\java");//目标项目(源码主路径)
javaModelGeneratorConfig.setTargetPackage("com.rj.domain");//目标包(Model类文件存放包)
javaModelGeneratorConfig.addProperty("enableSubPackages","false");
javaModelGeneratorConfig.addProperty("trimStrings","true");
context.setJavaModelGeneratorConfiguration(javaModelGeneratorConfig);
/*SqlMapper生成器配置(*Mapper.xml类文件),要javaClient生成器类型配合*/
SqlMapGeneratorConfiguration sqlMapGeneratorConfig = new SqlMapGeneratorConfiguration();
sqlMapGeneratorConfig.setTargetProject(parentPath + "\\dao\\src\\main\\java");//目标项目(源码主路径)
sqlMapGeneratorConfig.setTargetPackage("com.rj.mapper.xml");//目标包(*Mapper.xml类文件存放包)
sqlMapGeneratorConfig.addProperty("enableSubPackages","false");
context.setSqlMapGeneratorConfiguration(sqlMapGeneratorConfig);
/*JavaClient生成器配置(*Mapper.java类文件)*/
JavaClientGeneratorConfiguration javaClientGeneratorConfig = new JavaClientGeneratorConfiguration();
javaClientGeneratorConfig.setConfigurationType("XMLMAPPER");//JavaClient生成器类型(主要有ANNOTATEDMAPPER、MIXEDMAPPER、XMLMAPPER,要Context的TargetRuntime配合)
javaClientGeneratorConfig.setTargetProject(parentPath + "\\dao\\src\\main\\java");//目标项目(源码主路径)
javaClientGeneratorConfig.setTargetPackage("com.rj.mapper");//目标包(*Mapper.java类文件存放包)
javaClientGeneratorConfig.addProperty("enableSubPackages","false");
context.setJavaClientGeneratorConfiguration(javaClientGeneratorConfig);
/*表生成配置*/
TableConfiguration tableConfig = new TableConfiguration(context);
tableConfig.setTableName(tableName);
tableConfig.setCountByExampleStatementEnabled(true);
tableConfig.setUpdateByExampleStatementEnabled(true);
tableConfig.setDeleteByExampleStatementEnabled(true);
tableConfig.setInsertStatementEnabled(true);
tableConfig.setDeleteByPrimaryKeyStatementEnabled(true);
GeneratedKey generatedKey = new GeneratedKey(indexId, "MySql", true, null);//设置主键列和生成方式
tableConfig.setGeneratedKey(generatedKey);
context.addTableConfiguration(tableConfig);
Configuration config = new Configuration();
config.addContext(context);
return config;
}
}
注意:
再@SpringbootApplication
的位置配置@MapperSacan
的,我们不再使用spring的,而是使用tk.mybatis
的import tk.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication
@MapperScan("com.jd.mapper")
public class AppStart {
public static void main(String[] args) {
SpringApplication.run(AppStart.class, args);
}
}
10、关于Jackson的工具类
public class JackSonUtil {
/**
* 日志
*/
private static final Logger LOGGER = LoggerFactory.getLogger(JackSonUtil.class);
/**
* 将对象序列化
*/
public static String objToJson(Object obj) {
ObjectMapper mapper = new ObjectMapper();
String json = null;
try {
json = mapper.writeValueAsString(obj);
} catch (Exception e) {
LOGGER.error("序列化失败", e);
}
return json;
}
/**
* json串反序列化
*/
public static <T> T jsonToObj(String json, TypeReference<T> typeReference) {
ObjectMapper mapper = new ObjectMapper();
T t = null;
try {
t = mapper.readValue(json, typeReference);
} catch (Exception e) {
LOGGER.error("", e);
}
return t;
}
}
11、线程池+关于@Configuration
和@Bean
方式1
//@Configuration 创建自己的线程池,并交给spring管理
@Configuration
public class ThreadPoolConfig {
/**
* 核心线程数:使用yml配置文件注入
*/
@Value("${thread.core.size}")
private int corePoolSize;
/**
* 最大线程数
*/
@Value("${thread.max.size}")
private int maxPoolSize;
/**
* 最大等待时间(秒)
*/
@Value("${thread.alive.second}")
private int keepAlive;
/**
* 线程队列数
*/
@Value("${thread.queue.size}")
private int queueSize;
@Bean("threadPoolExecutor")
public ThreadPoolTaskExecutor getExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//核心线程数:线程池创建时候初始化的线程数
executor.setCorePoolSize(corePoolSize);
//最大线程数:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
executor.setMaxPoolSize(maxPoolSize);
//允许线程的空闲时间(秒):当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
executor.setKeepAliveSeconds(keepAlive);
//缓冲队列数量:用来缓冲执行任务的队列
executor.setQueueCapacity(queueSize);
//线程池对拒绝任务的处理策略:这里采用了CallerRunsPolicy策略,
// 当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;
// 如果执行程序已关闭,则会丢弃该任务
executor.setThreadNamePrefix("myThread-");//设置线程号前缀
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
}
//我们也可以使用yml配置文件注入参数
/**
* 核心线程数
*/
@Value("${mythread.core.size}")
private int CORE_POOL_SIZE;
//调用线程池:这里使用的是@Autowired,所以会根据类名去找,而与我们指定的bean名称id无关
//如果我们使用@Resource,则必须将这个类的名字与我们指定的@Bean的名称id一致
@Autowired
private ThreadPoolTaskExecutor threadpool1324251;
@Resource
private ThreadPoolTaskExecutor threadPoolExcutor;
//这样就可以直接使用线程池了
方式2
此外,我们还可以使用@ConfigurationProperties
#myThreadPool-config2
my:
threadpool2:
config:
coreSize: 5
maxSize: 10
queueSize: 20
aliveSconds: 300
@Data
@Configuration
//必须都是小写,且成员变量与yml配置中的一致,且必须和@Configuration一起用
@ConfigurationProperties(prefix = "my.threadpool2.config")
public class ThreadPoolConfigParam {
/**
* 核心线程
*/
private int coreSize;
/**
* 最大线程
*/
private int maxSize;
/**
* 队列
*/
private int queueSize;
/**
* 存活秒数
*/
private int aliveSconds;
}
直接引入参数配置线程池
@Configuration
public class ThreadPoolConfig {
/**
* 配置参数
*/
@Resource
private ThreadPoolConfigParam param;
/**
* 获取线程池
*/
@Bean("threadPoolTask")
public ThreadPoolTaskExecutor getExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(param.getCoreSize());
taskExecutor.setMaxPoolSize(param.getMaxSize());
taskExecutor.setQueueCapacity(param.getQueueSize());
taskExecutor.setKeepAliveSeconds(param.getAliveSconds());
taskExecutor.setThreadNamePrefix("threadPool2-");
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return taskExecutor;
}
}
方式3
在配置内部注入参数bean,将配置文件引入
@Configuration
public class ThreadPoolConfig {
/**
* 注入参数bean并将配置文件内容引入
*/
@Bean
@ConfigurationProperties(prefix = "my.threadpool2.config")
public ThreadPoolConfigParam getParam() {
return new ThreadPoolConfigParam();
}
/**
* 获取线程池
*/
@Bean("threadPoolTask")
public ThreadPoolTaskExecutor getExecutor() {
ThreadPoolConfigParam param = getParam();
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(param.getCoreSize());
taskExecutor.setMaxPoolSize(param.getMaxSize());
taskExecutor.setQueueCapacity(param.getQueueSize());
taskExecutor.setKeepAliveSeconds(param.getAliveSconds());
taskExecutor.setThreadNamePrefix("threadPool2-");
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return taskExecutor;
}
}
12、排包
这项操作非常重要,由于包冲突,导致项目启动失败,这里推荐使用插件
Maven Helper
进行包冲突排除,建议将低版本的jar包排除掉
项目启动会有错误,大多数问题是jar包冲突,不要慌张,可在在idea使用
shift+alt+ctrl+n
进行文件搜索,结合网上对问题的解决,确定是哪个jar包冲突导致,然后排包,注意版本号,maven-helper
提示的需要排除的红色依赖,不一定是要排除的,原则上应该是排除低版本;另外,spring生态的,尤其是boot-start/core等,保证版本号一致
另外,由于springBoot的集成,我们可以搜索需要使用的jar包是否已经被引入,如果版本合适,我们就不需要再单独引入了,否则需要单独引入需要版本,并排除boot集成的版本
使用springBoot的jar包的时候,如果除了应用
spring-boot-web-starter
以外,还引入其他的组件jar包,建议使用原生
,不必要使用boot-starter
集成的,以避免太多的冲突
13、springBoot配置拦截器
SpringBoot的WebMvcConfigurer
接口提供了很多针对过去springMVC控制层的非web.xml
配置方法。
首先创建两个拦截器配置,一个是权限校验,一个是usrPin的处理。
/**
* 权限拦截校验配置
* 必须实现HandlerInterceptor的拦截器接口
*/
@Service
@Slf4j
public class AuthorityInceptorConfig implements HandlerInterceptor {
/**
* 前置校验
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("访问controller前开始进行权限校验....");
return true;
}
}
/**
* 错误码校验:当访问返回401错误码的时候,直接跳转错误页
*/
@Service
@Slf4j
public class ErrorCodeInterceptor implements HandlerInterceptor {
/**
* 后置校验
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
if(response.getStatus()==401){
//如果是重定向,必须写【redirect】,否则默认是具体页面,而不是重定向到另一个controller
modelAndView.setViewName("redirect:/common/error401");
}
}
}
最后将两个权限配置注入到WebMvcConfigurer
/**
* 登录配置:
* 实现【WebMvcConfigurer】接口
* 该接口提供了很多的可以重写的方法,比如拦截器和主页设置
*/
@Configuration
public class LoginConfig implements WebMvcConfigurer {
/**
* 将权限校验拦截器注入
*/
@Resource
private AuthorityInceptorConfig authorityInceptorConfig;
/**
* 错误码校验
*/
@Resource
private ErrorCodeInterceptor errorCodeInterceptor;
/**
* 重写拦截器方法即可配置
* @param registry 注册拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
InterceptorRegistration registrationOfAuthor = registry.addInterceptor(authorityInceptorConfig);
registrationOfAuthor.addPathPatterns("/**"); //对所有路径拦截
/*
* 当然,我们可以继续配置多个registration
*/
InterceptorRegistration registrationOfError = registry.addInterceptor(errorCodeInterceptor);
registrationOfError.addPathPatterns("/**");
}
}
注意
:需要在springBoot启动位置配置注解:@ServletComponentScan(basePackages = {"com.jd.config.interceptor"})
打印结果:
[http-nio-8089-exec-1] INFO c.j.c.i.AuthorityInceptorConfig - 访问controller前开始进行权限校验…
14、SpringBoot主页设置
如果你使用的是SpringBoot自带的模板引擎thymeleaf
,那么必须保证pom版本一致。
"/**
* 登录controller
*/
@Controller
@Slf4j
public class LoginController {
/**
* 主页
*/
@RequestMapping("/index")
private String goToLogin() {
log.info("重定向到登录");
return "redirect:/common/login";
}
/**
* 登录主页
*/
@RequestMapping("/common/login")
private String login() {
log.info("跳转到页面");
return "forward:/index.html";
}
}
如果你在static
静态资源路径下配置了index.html
,那么直接使用localhost:8080
访问就可以直接访问,因为访问路径为"/"
。而如果你是在他路径下配置了首页,那么将访问不到,我们希望能针对"/"
进行重定向到"/index"
的controller,其功能类似于过去SpringMvc的web.xml
配置
<welcome-file-list>
<welcome-file>index</welcome-file>
</welcome-file-list>
实现方式同样是使用接口WebMvcConfigurer
,不过要重写另一个方法addViewControllers
/**
* 设置主页,任何以"/"访问的都转为"/index"再访问主页
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addRedirectViewController("/", "index");
registry.setOrder(Ordered.HIGHEST_PRECEDENCE); //最高级别,过滤的时候最先执行
}
访问
localhost:8080
打印结果:
—INFO c.j.c.LoginController - 重定向到登录
—INFO c.j.c.LoginController - 跳转到页面
而且地址栏也自动变成了我们想要的:http://localhost:8089/common/login
15、SpringBoot过滤器配置
以往配置springMvc的过滤器配置都是使用的web.xml
,
<filter>
<filter-name>sessionFilter</filter-name>
<!--创建一个SessionFilter继承Filter-->
<filter-class>com.rj.web.filter.SessionFilter</filter-class>
<init-param>
<!--设置排除路径-->
<param-name>extendUrl</param-name>
<param-value>
/index,/common/login
</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>sessionFilter</filter-name>
<url-pattern>/*</url-pattern><!--拦截路径:全部-->
</filter-mapping>
现在同样使用继承Filter
方式,但是参数可以通过注解注入。
/**
* session过滤:
* 1、本地启动的时候,式springboot先启动,故配置文件内容可以在filter抓取到
* 2、但是,如果是线上容器,则可能tomcat先启动,然后触发springboot,这将导致nullPointerException
* 3、所以,我们建议将初始化参数通过【initParams】提前注入,然后在init方法中通过【filterConfig】获取
*/
@Order(1) //第一个过滤器,可以设置多个
@WebFilter(filterName = "sessionFilter", urlPatterns = "/*", initParams = { //通过InitParams将参数提早注入
@WebInitParam(name = "extendUrl", value = "/index,/common/login"), //排除需要过滤的
@WebInitParam(name = "sympol", value = "MaSaKi")
})
@Slf4j
public class SessionFilter implements Filter {
/**
* 新建一个list,初始化的时候将需要排除校验的url放入,在销毁的时候销毁
*/
private Set<String> extendUrls = Sets.newHashSet();
/**
* 初始化
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String extendUrl = filterConfig.getInitParameter("extendUrl"); //获取注解配置的initParam
extendUrls = Sets.newHashSet(Arrays.asList(extendUrl.split(","))); //初始化extencUrls
log.info("初始化排除过滤的 URL = {}", JackSonUtil.objToJson(extendUrls));
}
/**
* 执行过滤
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = null;
HttpServletResponse res = null;
if (request instanceof HttpServletRequest) {
req = (HttpServletRequest) request;
} else {
log.error("不是http请求!" );
return;
}
if (response instanceof HttpServletResponse) {
res = (HttpServletResponse) response;
} else {
log.error("不是http响应!" );
return;
}
//获取请求路径
String servletPath = req.getServletPath();
log.info("获取到的请求路径, servletPath = {}", servletPath);
//包含,直接继续
if (extendUrls.contains(servletPath)) {
chain.doFilter(req, res);
} else {
log.info("进行进一步过滤...");
chain.doFilter(req, res);
}
}
/**
* 销毁
*/
@Override
public void destroy() {
log.info("销毁extendUrls的set集合");
extendUrls.clear();
}
}
需要在SpringBoot启动类上打注解:@ServletComponentScan(basePackages = {"com.jd.config.interceptor","com.jd.config.filter"})
访问
localhost:8080
打印结果:
c.j.c.f.SessionFilter - 初始化排除过滤的 URL = ["/common/login","/index"]
c.j.c.f.SessionFilter - 获取到的请求路径, servletPath = /index
16、Spring的异步监听机制
类似于消息队列,但是在内存处理的,并且不能像消息队列那样可以限流削峰,可以说与开线程一样,但是可以解耦,对于数据的并行处理非常有用。
我们有一个销售商品的接口,主线任务是销售,副线任务是比较销售额:
/**
* 商品销售的实现类
*/
@Service
@Slf4j
public class GoodSaleServiceImpl implements GoodSaleService {
/**
* spring 应用上下文
*/
@Resource
private ApplicationContext applicationContext;
/**
* 进行销售
* 两份商品销售,最后需要异步发送消息去进行销售额的比较
*/
@Override
public void saling() {
//苹果手机销售
Goods apple = new Goods();
apple.setGoodName("iPhone12Pro");
apple.setGoodPrice("10000"); //一部一万块
int appleSaleNum = 100; //销售了一百部
//华为手机销售
Goods huawei = new Goods();
huawei.setGoodPrice("7000");
huawei.setGoodName("Mate70s");
int huaweiSaleNum = 1000;
log.info("苹果销售数量是{}部, 华为手机销量是{}部", appleSaleNum, huaweiSaleNum);
//使用spring的监听推送消息
applicationContext.publishEvent(new PriceComparatorEvent(
apple,
huawei,
appleSaleNum,
huaweiSaleNum));
}
}
我们需要创建一个监听类PriceComparatorEvent
,Spring也是通过类型去判断由谁监听。
/**
* 销售额比较事件:spring的监听就是通过这个类来确认接受者是谁
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PriceComparatorEvent {
/**
* 苹果货品
*/
private Goods apple;
/**
* 华为货品
*/
private Goods huawei;
/**
* 苹果销量
*/
private int appleNum;
/**
* 华为销量
*/
private int huaweiNum;
}
最后我们需要设置一个监听者去异步执行逻辑
/**
* 价格比较监听者
*/
@Component //确保springboot扫描到
@Slf4j
public class PriceComparatorListener {
/**
* 进行事件监听
*/
@Async("threadPoolUtil") //异步监听,建议使用自己的线程池,我设置的线程号前缀是【myThread-】
@EventListener //监听注解,将根据参数类型查找监听者
public void onEventListening(PriceComparatorEvent priceComparatorEvent) {
log.info("开始比价处理-----");
Goods apple = priceComparatorEvent.getApple();
int appleNum = priceComparatorEvent.getAppleNum();
Goods huawei = priceComparatorEvent.getHuawei();
int huaweiNum = priceComparatorEvent.getHuaweiNum();
log.info("苹果销售额是{}元, 华为销售额是{}元",
Integer.valueOf(apple.getGoodPrice())*appleNum,
Integer.valueOf(huawei.getGoodPrice())*huaweiNum);
}
}
同样,我们需要在SpringBoot启动类加注解允许异步,否则就只是解耦,还是同步的调用:@EnableAsync
打印结果,走的是单独的自定义线程:
[myThread-0] INFO c.j.l.PriceComparatorListener - 苹果销售额是1000000元, 华为销售额是7000000元