文章目录
1. 搭建流程
1.引入Spring-Boot依赖、Spring-Boot整合MyBatis的插件依赖、MyBatis由数据库表逆向生成JavaBean和Mapper文件的mybatis-generator插件等:
<?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>
<groupId>com.glodon.tot.library</groupId>
<artifactId>mybatis-tot-library</artifactId>
<version>1.0-SNAPSHOT</version>
<!--SpringBoot的基本父级依赖-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.5.RELEASE</version>
</parent>
<!--全局属性模板配置-->
<properties>
<java.version>1.8</java.version>
<mybatis.version>3.4.0</mybatis.version>
<mybatis.generator.version>1.3.2</mybatis.generator.version>
<mybatis.springboot.version>1.1.1</mybatis.springboot.version>
</properties>
<dependencies>
<!--springboot对web项目的支持-->
<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>${mybatis.springboot.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!--SpringBoot整合Maven的依赖-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!--Maven的逆向工程插件-->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
<executions>
<execution>
<id>Generate MyBatis Artifacts</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>${mybatis.generator.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
2.resources目录下配置SpringBoot的配置文件和逆向工程插件mybatis-generator的配置文件,主要如下:
[application.yml]
spring:
application:
name: mybatis-spring-demo-tot
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/glodon_test
username: root
password: root
[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>
<!--导入所需的jar,我们的jar都在Module的路径下,这一句可以省去
<classPathEntry location="/Program Files/IBM/SQLLIB/java/db2java.zip" />
-->
<!--这里的targetRuntime是指定生成SQL映射文件的类型:
MyBatis3:可以生成动态增删改查的映射文件;
MyBatis3Simple:可以生成简单带有增删改查的Mapper的SQL标签
其他值看文档
-->
<context id="DB2Tables" targetRuntime="MyBatis3Simple">
<!--jdbcConnection指定如何连到数据库,就是配置数据库连接-->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/glodon_test"
userId="root"
password="root">
</jdbcConnection>
<!--Java类型解析器,一般使用默认-->
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!--指定JavaBean的生成策略
targetPackage:指定生成JavaBean的目标包名
targetProject:指定目标工程
-->
<javaModelGenerator targetPackage="com.glodon.tot.models" targetProject="src/main/java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!--SQL映射文件的生成策略,即mapper文件
targetPackage:目标包,不需要手动创建
targetProject:目标工程
targetPackage包名必须和下面javaClientGenerator标签中targetPackage一致
-->
<sqlMapGenerator targetPackage="com.glodon.tot.mappers" targetProject="src/main/resources">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!--指定接口生成策略,即Dao层-->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.glodon.tot.mappers" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<!--指定逆向分析哪些表,创建JavaBean
domainObjectName:指定对应表的JavaBean的类名
-->
<table tableName="books" domainObjectName="Books"></table>
<table tableName="buser" domainObjectName="Buser"></table>
<table tableName="bookrecords" domainObjectName="BookRecords"></table>
</context>
</generatorConfiguration>
上面是我之前的基本配置(如果mybatis的maven插件报红重启IDEA即可),然后通过侧边菜单栏Maven Projects中Plugins中的mybatis-generator插件运行即可生成数据库中表的mapper接口和接口对应的映射文件和javaBean对象,的下面有一个差不多的配置文件,也可参照:
[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="DB2Tables" defaultModelType="flat" targetRuntime="MyBatis3Simple">
<commentGenerator>
<!-- 抑制警告 -->
<property name="suppressTypeWarnings" value="true"/>
<property name="suppressDate" value="true"/>
<property name="suppressAllComments" value="false"/>
<property name="javaFileEncoding" value="UTF-8"/>
</commentGenerator>
<!--数据库链接地址账号密码-->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://10.1.83.41:3306/demo?useUnicode=true&characterEncoding=utf8"
userId="root" password="root">
</jdbcConnection>
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!--生成Model类存放位置-->
<javaModelGenerator targetPackage="com.glodon.demo.mybatis.models" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
<property name="trimStrings" value="true"/>
<!--<property name="constructorBased" value="true"/>-->
</javaModelGenerator>
<!--生成映射文件存放位置-->
<sqlMapGenerator targetPackage="com.glodon.demo.mybatis.mappers" targetProject="src/main/resources">
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<!--生成Dao类存放位置-->
<!-- 客户端代码,生成易于使用的针对Model对象和XML配置文件 的代码
type="ANNOTATEDMAPPER",生成Java Model 和基于注解的Mapper对象
type="MIXEDMAPPER",生成基于注解的Java Model 和相应的Mapper对象
type="XMLMAPPER",生成SQLMap XML文件和独立的Mapper接口
-->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.glodon.demo.mybatis.mappers" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<table tableName="user" domainObjectName="User"
enableCountByExample="true"
enableUpdateByExample="true"
enableDeleteByExample="true"
enableSelectByExample="true"
selectByExampleQueryId="true">
<generatedKey column="id" sqlStatement="JDBC"/>
<columnOverride column="sex" javaType="com.glodon.demo.mybatis.models.Sex" />
</table>
</context>
</generatorConfiguration>
3.创建SpringBoot的入口类,该类会自动扫描它所在目录及子级目录,所以注意一下它的位置。
@SpringBootApplication
@MapperScan("com.glodon.tot.mappers") //指定mapper接口的扫描路径
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
4.利用maven插件生成对应的JavaBean和Mapper接口以及对应的mapper文件,在创建好数据的表后,直接在Maven Projects侧边菜单栏找到Plugins–>mybatis-generator–>mybatis-generator:generate运行即可生成。
5.根据业务需求进行开发,注解啥的都跟Spring和SpringMVC一样,但是Controller类上的注解由@Controller
变成了@RestController
,其他一样。
2. 常见问题
2.1 Springboot创建非web应用
SpringBoot除了可以用来写API,也可以用来直接作应用,只是这个时候需要改动Spring-Boot的启动类,继承CommandLineRunner
类,只是不用写Controller层了,如下:
/**
* @author liuwg-a
* @date 2018/12/18 20:32
* @description
*/
@SpringBootApplication
public class Application implements CommandLineRunner {
@Autowired
DownloadService downloadService;
public static void main(String[] args) {
// 这里的args一定不能掉,否则无法获取参数
SpringApplication.run(Application.class, args);
}
// 接受命令行中的参数
@Override
public void run(String... args) {
if (args.length!=1) {
throw new IllegalArgumentException("Please input only one path that you want to store!");
}
// 调用的时候直接调用Service层即可,即不用再写Controller层了
downloadService.getAllPublicRfas(args);
exit(0);
}
}
2.2 打包Springboot项目,运行jar包提示“jar中没有主清单属性”
引入插件即可解决,可以自动添加清单,如下:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.0.3.RELEASE</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
2.3 让springboot在控制台打印MyBatis的日志
在springboot的配置文件application.properties
(如果是yml文件具体更改格式)中写入:
# 让MyBatis打印日志
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
指定某个模块使用其他多个模块中的mapper.xml文件,在配置文件中application.properties
(如果是yml文件具体更改格式)中写入:
# mybatis
mybatis.mapperLocations=classpath:com/goujianwu/portal/common/mapper/*.xml
mybatis.typeAliasesPackage=com.goujianwu.portal.common.model
# 引用多个模块的mapper文件
mybatis.mapperLocations=classpath*:mapper/*.xml
2.4 springboot时间修改时区后接口返回时间不变
在使用VOD时,UTC转成北京时间时,由于序列化的原因时间不论怎么转,控制台打印出来的是北京时间,但PostMan使用得到的还是UTC的时间,主要原因是Spring默认的序列化框架FastJson的问题,所以建议配置文件中加入:
# 配置序列化时时区
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
2.5 springboot中Bean的循环依赖
在一次项目中出现这样下面的情况:
@Service("aService")
public class AServiceImpl implements AService {
@Autowired
private BService;
//...
}
@Service("bService")
public class BServiceImpl implements BService {
@Autowired
private AService;
//...
}
启动项目时出现:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'testCallbackController': Unsatisfied dependency expressed through field 'AService'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'AService': Bean with name 'AService' has been injected into other beans [bService] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:584)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:370)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1336)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:572)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:759)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:548)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:142)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:386)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:307)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1242)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1230)
at com.goujianwu.task.Application.main(Application.java:23)
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'AService': Bean with name 'AService' has been injected into other beans [bService] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:602)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:251)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1135)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1062)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:581)
出现提示AService和BService循环依赖,项目无法启动,有点类似于死锁的场景,Spring在初始化容器时会将所有Bean对象进行初始化,在初始化AService
对象,发现需要初始化BService
,但在初始化时发现又要初始化AService
,Spring就有点懵逼了,这特么到底要宝宝先初始化哪个Bean啊,所以会出现上述的启动异常。
解决方案:通过将其中一个Bean设成懒加载即可,如:
@Lazy
@Service("aService")
public class AServiceImpl implements AService {
@Autowired
private BService bService;
//...
}
@Service("bService")
public class BServiceImpl implements BService {
@Autowired
private AService aService;
//...
}
但通常不会这么简单,我们发现有很多Controller
用到了aService
和bService
,就算将上述两个Service的Bean设成懒加载了,在Spring的IOC容器初始化后实例化Controller的Bean时,发现不少Controller通过@Autowired
注入了aService
和bService
,所以上述的方法极少会解决问题,更好的方式应该在出现循环依赖的Bean中注入另一个Bean时同时指定为懒加载,另一个不变:
@Service("aService")
public class AServiceImpl implements AService {
@Lazy
@Autowired
private BService bService;
//...
}
@Service("bService")
public class BServiceImpl implements BService {
@Autowired
private AService aService;
//...
}
这样一来就算加载Controller时注入aService
和bService
,就不会有影响,比如初始化aService
时,并不会去初始化bService
,只有在调用aService
中的某个涉及bService
的具体方法时才会去初始化bService
,至此问题解决(其实还是建议尽量减少代理之间的耦合度,良好的代码设计才是规避循环依赖的最好方式)。
2.6 springboot中开启定时任务支持
SpringBoot启动类上添加@EnableScheduling
注解开启支持:
@SpringBootApplication
@EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
使用的时候只需要在Bean中的方法上加上注解@Scheduled
注解即可:
@Scheduled(cron = "0/1 0 20 * * *")
protected void run() {
...
}
该注解支持多种参数,上面是仅是利用的cron
表达式cron = x x x x x x x
,个人觉得在简单场景下使用较为方便,表达式中的7个x
从前至后分别表示秒、分、时、日、月、星期几、年份(可选参数),下面提供cron
参数的说明:
参数 | 说明 |
---|---|
第1位x | 表示秒,取值范围0-59 |
第2位x | 表示分,取值范围0-59 |
第3位x | 表示时,取值范围0-23 |
第4位x | 表示日,取值范围1-31 |
第5位x | 表示月份,取值范围1-12 |
第6位x | 表示星期几,取值范围1-7 (注:从周日开始算,即表示周日-周六) |
第7位x | 表示年份(可选),取值范围1970-2099 |
注:
此外,上述7位参数还支持如下取值:
*
:可以匹配任意值,具体范围根据所处参数位置判断,可以理解为每秒,每分,每时…?
:表示不确定的值,它只能出现在日和星期几中,即第4、6位参数中;-
:表示取值区间,包含首尾,如2-5
,表示了2
、3
、4
、5
4个值;,
:表示一个列表,可以理解为-
一个意思,只是它可以取到间断不连续的值,比如2,4,6
;/
:语法为x/y
,x
表示从x
开始,y
表示步进值;#
:表示第几个,只能出现星期几中,即第6位参数,比如1#2
,表示第2周中的周日;
下面是示例:
0 0 3 * * ? 每天3点触发任务
0 5 3 * * ? 每天3点5分触发任务
0 5 3 ? * * 每天3点5分触发任务,其中日使用?表示不确定值,因为可能为28、30、31
0 5/10 3 * * ? 每天3点5分触发任务,并且期间每隔10分钟周期性执行一次,直到3点55结束当天任务,次日再次重复
0 10 3 ? * 1 每周星期日的3点10分触发任务
0 10 3 ? * 1#3 每个月的第三周中的星期天触发任务,#号只能出现在星期几的位置上;
啰嗦一句,不要写代码写傻了,和我一样想不通@Scheduled(cron = "0/1 0 20 * * *")
为什么在晚上8点触发任务后,每一秒都执行一次,为什么只能执行到20:00:59
,不继续往下运行了,因为分钟位置上限制了为0
,即20:01:00
后就不在触发任务了,触发将其改作0-1
等(其他表达式效果一样即可)。
2.7 springboot打包成jar运行找不到 resources 下文件
SpringBoot 打包成 jar 后无法读取 resources 下的文件,必须以流的方式读取,否则会抛文件未找到的异常,示例代码如下:
public String uEditorOption() {
// springboot打包后只能以流的方式读取jar包classes下的文件
StringBuilder config = new StringBuilder();
try {
InputStream stream = this.getClass().getClassLoader().getResourceAsStream("ueditor/config.json");
if (stream == null) {
return null;
}
BufferedReader br = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));
String line;
while ((line = br.readLine()) != null) {
config.append(line);
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return config.toString();
}
2.8 springboot 中获取上下文
实现 ApplicationContextAware
类即可,在实现方法 setApplicationContext(ApplicationContext applicationContext)
时,可以将其拷贝到自己本地,示例如下:
public class ApplicationContextHolder implements ApplicationContextAware {
private static ApplicationContext contextContainer;
private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationContextHolder.class);
public static ApplicationContext getContextContainer() {
LOGGER.info(">> spring context has init.");
return contextContainer;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
contextContainer = applicationContext;
}
}
2.9 springboot 中获取带有指定注解的 Bean 对象
通过上下文的 getBeansWithAnnotation()
方法可以获取带有指定注解的 Bean
对象,然后通过 Spring 的 AnnotationUtils.findAnnotation(x,x)
方法获取注解对象,最后通过这个注解对象获取上面的值。千万不要用 AnnotationUtils.getAnnotation
方法去获取,那样获取的是代理对象,无法获取实际 Bean 对象,正确示例如下:
Map<String, Object> beansWithAnnotation = springApplicationContext.getBeansWithAnnotation(EnableExcelImportSupport.class);
if (CollectionUtils.isEmpty(beansWithAnnotation)) {
return;
}
beansWithAnnotation.forEach((key, value) -> {
Class<?> aClass = value.getClass();
EnableExcelImportSupport annotation = AnnotationUtils.findAnnotation(aClass, EnableExcelImportSupport.class);
if (annotation != null && annotation.types() != null && annotation.types().length > 0) {
Arrays.asList(annotation.types()).forEach(o -> templateTypes.put(o.getType(), o));
}
});
2.10 组合注解
这个其实是基础内容,不完全是 spring 的内容,某些时候我们需要将自定义的注解和已有的注解进行组合,以便让这个注解可以具备多个注解的功能,比如当我们自定义的注解,但若同时想让 Spring 来管理带有这个注解的类的实例,那我们需要自己去实现 Spring 的扩展接口来扫描自定义注解来让带有自定义注解的类的实例加入 IOC 容器,但此时若我们可以将自定义注解和Bean对象的注解如:@Bean
、@Component
等组合一下,就不用那么复杂自己去实现扩展进行扫描,它就会自动交给 IOC 容器了。示例如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Component // 和Spring的Bean管理注解合并,不能少
public @interface CustomExcelExportHandler {
ExcelExportTaskBizType type();
}