**首次分享不喜勿喷,工作2年多,想换工作来着,结果出去面试发现自己落伍太多,也明白自己懒惰了太多了,在这个环境光中太安逸了,自感如此下去会被这个行业所淘汰,所以奋起学习,只希望不晚!好了废话不说直接开始。**
项目名称:MailProducer (邮件的发送和接受系统)
框架:Spring ,Spring MVC ,Mybatis
数据库:mysql(主从复制,稍后会附上mysql主从复制的文章)
缓存数据库:redis
消息中间件:redis,
数据连接池:druid
代码地址:代码上传至码云,欢迎诸位下载,如发现问题,欢迎随时骚扰,共同学习
本篇文章大致会分为如下几个部分,目录结构,pom.xm文件讲解,application.yml文件讲解,代码讲解(注:本次文章为学习的第一阶段总结,后续会持续更新,如有问题,可以随时提问)
目录结构
以上就是目录结构,对于每个类的用途 会在代码分析阶段进行细说,此处不做赘述。
其一 database 这个包是关于主从数据源的连接以及如何切换主从数据源
其二 mapping包在Spring Boot 项目中,官方建议是放在resources中,但也可以放在java中,这个看个人,不用疑惑
其三 service.api这个包就相当与MVC模式中的controller层,或者大家可以理解就是controller
其四 entiy 这个包是实体类
其五 util 这个包是工具包
resources目录中目前只有一个application.yml配置,这个也是Spring Boot 的核心点,只需要这一个配置文件我们就可以快速的创建一个简单的Spring Boot,大大的简化了Spring 项目的开发,这就是为把Spring Boot项目 称之为微服务项目的原因。
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>
<groupId>com.mail</groupId>
<artifactId>MailProducer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>MailProducer</name>
<description>MailProducer</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<commons-fileupload.version>1.2.2</commons-fileupload.version>
<commons-lang3.version>3.3.1</commons-lang3.version>
<commons-io.version>2.4</commons-io.version>
<commons-collections.version>3.2.2</commons-collections.version>
<fastjson.version>1.1.26</fastjson.version>
<mybatis.version>3.4.1</mybatis.version>
<mybatis-spring.version>1.3.0</mybatis-spring.version>
<druid.version>1.0.24</druid.version>
<fasterxml.uuid.version>3.1.4</fasterxml.uuid.version>
<github.miemiedev.version>1.2.17</github.miemiedev.version>
<common.codec.version>1.10</common.codec.version>
<servlet-api.version>3.1.0</servlet-api.version>
<commons-beanutils.version>1.9.3</commons-beanutils.version>
<org.codehaus.jackson.version>1.9.13</org.codehaus.jackson.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency><!-- web -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency> <!-- aop 切面编程 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency><!-- 自动配置类 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency><!-- 使用jsp时会用到表达式 -->
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency><!-- 使用servlet会用到 -->
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency><!-- Apache的commons-fileupload.jar可方便的实现文件的上传功能 -->
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>${commons-fileupload.version}</version>
</dependency>
<dependency><!-- 跟java.lang这个包的作用类似,Commons Lang这一组API也是提供一些基础的、通用的操作和处理 -->
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!--对json格式的支持 -->
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>${org.codehaus.jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- uuid -->
<dependency><!-- 生成uuid,根据时间等,避免重复 -->
<groupId>com.fasterxml.uuid</groupId>
<artifactId>java-uuid-generator</artifactId>
<version>${fasterxml.uuid.version}</version>
</dependency>
<!-- mail -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- 添加JDBC jar -->
<dependency><!--spring提供的jdbc包 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency><!-- spring提供的mybatis jar包-->
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<dependency><!-- tkmybatis是在mybatis框架的基础上提供了很多工具,让开发更加高效,更多功能请自行探讨,本人也是首次用 -->
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>1.1.0</version>
</dependency>
<dependency><!-- 数据库连接池 -->
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency><!-- mysql驱动包,此处有一个疑问就是5.1.41用在此处的时候 在创建sqlSessionFactory的bean时候,总是报找不到sqlSessionFactory,换到5.1.45正常,有知道原因的求解?-->
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
</dependency>
<!--pagehelper-->
<!-- <dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.1.0</version>
<exclusions>
<exclusion>
<artifactId>mybatis-spring-boot-starter</artifactId>
<groupId>org.mybatis.spring.boot</groupId>
</exclusion>
</exclusions>
</dependency> -->
<!-- spring redis -->
<!-- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
<version>1.4.3.RELEASE</version>
</dependency> -->
<dependency><!-- 未知,后续在解释 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<finalName>mail-producer</finalName>
<!-- 打包时包含properties、xml -->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<!-- 是否替换资源中的属性-->
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
</plugin>
<!-- 解解决maven update project 后版本降低为1.5的bug -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<!-- 单元测试 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
<includes>
<include>**/*Test*.java</include>
</includes>
<testFailureIgnore>true</testFailureIgnore>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<!-- 绑定到特定的生命周期之后,运行maven-source-pluin 运行目标为jar-no-fork -->
<execution>
<phase>package</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
上述是完整的pom.xml文件,如果有疑问,欢迎随时骚扰。
application.yml文件讲解
server:
context-path: /mail-producer #根路径
session:
timeout: 900 #session管控,session时长 秒
port: 8001
spring: ## Spring配置:
http:
encoding:
charset: UTF-8
jackson:
date-format: yyyy-MM-dd HH:mm:ss #自动格式化json中date类型
time-zone: GMT+8
default-property-inclusion: NON_NULL #如果参数有空或者为Null为自动过滤
# redis:
# jedis:
# pool:
# min-idle: 100
# max-idle: 100
# max-wait: -1
# max-active: 1000
# timeout: 6000
# cluster:
# max-redirects: 1000
# nodes:
# - 192.168.1.115:7001
# - 192.168.1.115:7002
# - 192.168.1.115:7003
# - 192.168.1.115:7004
# - 192.168.1.115:7005
# - 192.168.1.115:7006
mail:
default-encoding: UTF-8
host: smtp.163.com
port: 25
username:
password:
properties:
mail:
smtp:
auth: true
timeout: 30000
#数据连接管理
druid:
type: com.alibaba.druid.pool.DruidDataSource #数据管理类
master:
url: jdbc:mysql://192.168.199.216:3306/mail-1.0?characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
initialSize: 5
#此处开始为druid的配置
minIdle: 1
#maxIdle: 10
maxActive: 100
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
#filters: stat,wall,log4j #监控界面
useGlobalDataSourceStat: true
slave:
url: jdbc:mysql://192.168.199.193:3306/mail-1.0?characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true
driver-class-name: com.mysql.jdbc.Driver
username: root
password: 123456
initialSize: 5
minIdle: 1
#maxIdle: 10
maxActive: 100
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
#filters: stat,wall,log4j
useGlobalDataSourceStat: true
#后面这三个配置可以直接抓过去,修改别名和locations地址就可以了
mybatis:
type-aliases-package: com.mail.model
mapper-locations: classpath:com/mail/mapping/*.xml
logging:
level:
tk.mybatis: TRACE
pagehelper:
helperDialect: mysql
reasonable: true
supportMethodsArguments: true
params: count=countSql
上述是全部的application.yml文件的配置,在Spring Boot 的配置文件中大家可以看到这种yml文件是呈现一个树状结构,可以很清楚的反应配置结构,同时也方便大家看到这个参数数属于哪种配置。
当中有一段是mail的配置,是邮件系统使用,这段如果有问题,本篇先不做解释,后续用到会做出详细解释
后面这三个配置可以直接抓过去,修改别名和locations地址就可以了
mybatis:
type-aliases-package: com.mail.model #这个是别名 相信各位都可以理解,就不做赘述了
mapper-locations: classpath:com/mail/mapping/*.xml #这里主要是配置mapping的路径
logging:
level:
tk.mybatis: TRACE 配置tk.mybatis的日志输出
pagehelper: #配置分页的信息
helperDialect: mysql
reasonable: true
supportMethodsArguments: true
params: count=countSql
代码解释
下面我下先对database这个包下面的文件
MailProducerApplication.java 这个就是Spring Boot 的启动类,从官网上直接导出Spring Boot 项目还是从Ecplise 中直接新建项目都会有这个,这个是Spring Boot 项目的入口程序
MainConfig.java 这个类其实就是一个配置类,我在这个类里面放的是一些公共的bean ,
这个程序当中我什么也没有写,一个空类,
@Configuration: 但凡有这个注解的类都是告诉Spring Boot ,嗨,我是一个启动类,你启动的时候记得加载我,
这里有一篇关于
@EnableWebMvc: 启动了SpringMVC 前面,介绍说过我们本篇使用的框架中就有SpringMVC ,
@ComponentScan:全局扫描器,告诉Spring 哪个packages 的用注解标识的类 会被spring自动扫描并且装入bean容器,如果加有类似Controller这种注解类,没有被Spring Boot 启动时扫描到并至于Spring 容器中去,那你建立的这个Controller就没有了任何意义.这里我先扫描了一个mapper类
这里有一篇关于@ComponentScan的文章大家可以去看看,https://blog.csdn.net/u013078669/article/details/52664779
const.java这个是一个常量类,定义了一些通用的常量。
KeyUtil.java这个类就是生成一个uuid
不知道大家有没有看pom.xml文件,当中依赖了这个一个包
<dependency><!-- 生成uuid,根据时间等,避免重复 -->
<groupId>com.fasterxml.uuid</groupId>
<artifactId>java-uuid-generator</artifactId>
<version>${fasterxml.uuid.version}</version>
</dependency>
这个包是有阿里巴巴提供的一个fasetxml.uuid,是专门用来生成uuid的一个jar,
Generators这个类调用timeBasedGenerator这个方法,当中注入EthernetAddress.fromInterface()时区,最后在调用generate().toString() 就生了一个字符串类型的uuid,这个uuid是根据时区的,具体到了是毫秒好像,这样排序也好牌,也不会重复,很好用的一个uuid生成工具。
FastJsonConvertUtil.java 这里面是一些关于json转普通java对象字符串,或者是普通对象转json对象的一些方法,有兴趣的可以下载源码自己看看
接下来是本篇文章的重点。连接两个数据源,并完成主从的切换,且线程安全
要完成主从切换,首先得连接两个数据源,说到此处,突然想起一个问题,就是在此项目中使用5.1.41这个版本的mysql驱动包失败,说是找不到sqlsessionFactory,楼主最后在同事提醒下换成了5.1.45这个版本的mysql驱动包正常了,这个具体是不是兼容性还是说有Jar包冲突还未知,
好了回归正题,首先我们需要建立一个配置类,里面配置两个bean,一个主数据库源,一个是从数据源,就好比spring项目中配置文件,里面首先创建一个bean是主数据源,另外一个bean是从数据源,
我们说要创建一个配置类,因为这是创建两个bean,主从数据源,这两个数据源在Spring Boot 项目初始化的时候就主动加载进去,所以我们又要使用@Configuration这个注解了,大家注意了,
下面是申明一个Druid类型的数据源,我们可以去yml文件中查看
druid:
type: com.alibaba.druid.pool.DruidDataSource
@Value("${druid.type}")
private Class<? extends DataSource> dataSourceType;
?表示获取的这个druid.type的值, 并且继承DataSouce,并转换为Class,
@Value这是Spring Boot 特有的一个读取配置文件的注解
这里面maseterDataSouce是主数据源,slaveDataSource是从数据源,上面给大家说过这个配置类的作用就是建立两个bean, name就是给这个bean起个名字,@ConfigurationProperties 这个其实从单词上大家也能猜个差不多,就是获取配置文件yml中的某些配置,prefix这个指前缀,那个durid.master,就是指获取所有的druid.master开头的所有的yml里面的配置,
@primary 大家都知道本篇我们有两个数据源,这个注解启动就是表示这个优先加载而已
DataSource masterDataSource = DataSourceBuilder.create().type(dataSourceType).build();
通过DataSourceBuilder调用create(),在注入数据源类型,然后调用build我们就可以创建出一个数据源了
从数据源的除了名气,其他的主数据源一摸一样,
下面还有两个bean是配置druid的sql监控界面的servlet和filter
关于这一部分有兴趣的可以去网上搜,这里就不做具体解释了。
到了这里我们的数据源就已经设置完毕了,可以去启动Spring Boot 项目了,是可以正常启动的。建议大家去测试一下,
另外呢,我们这里配置了druid的SQL 监控平台,监控平台的访问地址 ip:端口/druid 就可以访问
建立了数据源之后,我们接下来需要考虑的问题是,俩个数据源,要切换,还要保证不同操作之间可以获取所需的数据源,不会出现线程的不安全性?请继续往下看?·
请看图片,这里采用的是枚举类型,建立两个主从的标识至于枚举方法中,
看到这里大家心里肯定都明白我们有连个数据源,那么为了各个线程之间互不影响,保证每个线程的安全性,这里我们采用ThreadLocal(线程局部变量)来存储数据源,下面的set方法就是把某个数据源添加入ThreadLocal中,get当然就是获取了,最后使用完这个数据源之后一个记得移除,不影响下一个线程使用重新设置数据源。
局部变量设置好了,接下来是本篇文章中最后一部分,配置 SqlSessionFactory 的bean,主要类是 MybatisConfiguration.java
这个类必须继承 MybatisAutoConfiguration ,如此我么可以直接调用父类中的 sqlSessionFactory 。
这个类我们主要是进行 SqlSessionFactory 的创建。首先我们照例来解释一下各个注解;
@Configuration 配置类,(本篇多次提起哦)
@AutoConfigurationAfter(DataSourceConfiguration.class) 这个注解意思是 这个配置类在 DataSourceConfiguration 类之后加载,理由很简单,因为要创建SqlSessionFactory 需要数据源嘛。。。。。
首先我们创建一个 bean, SqlSessionFacotry ,这里我们因为我们继承了MybatisAutoConfiguration,所以我们直接从父类中调用就可以,那么此处涉及到一个问题,就是注入数据源,
对于数据源的处理我们利用org.springframework.jdbc.datasource.lookup包下面的AbstractRoutingDataSource.class这个类来管理两个数据源,这类是一个路由类,
此处我们在在创建一类叫做ReadWriteSplitRoutingDataSource,这个类需要继承AbstractRoutingDataSource 并且重写了如下方法
@Override
protected Object determineCurrentLookupKey() {
// TODO Auto-generated method stub
return DataSourceContextHolder.getDataBaseType();
}
这里返回是具体的数据源(即ThreadLocal里面是主数据源还是从数据源)
这里大家可能后有点绕,先往下看,
再回到MybatisConfiguration.java中大家可以我还创建了第二个方法,目的就是把连个数据源先放到这个路由当中,然后由这个路由动态来分配数据源,可能有人看着会有点懵,那我就简单的解释,就把这个AbstractRoutingDataSource当作一个代理,我把两个数据源交给他,而大家也可以看到两个数据源是以map类型注入到这个AbstractRoutingDataSource类中,
当获取到ThreadLocal的值之后,在和key进行比对,然后输出具体的数据源,在把这个数据源注入到SqlSessionFactory 中。至此Mybatis 的SqlSessionFactory创建完毕。
那么我们还需要考虑一个问题,我们如何切换主从数据源呢,
为了解决这个问题,我们这里使用AOP来解决,大家是不是心里有一点眉目了,哈哈哈,下面给大家一一简答
这里我们采用自定义注解
/**
*
* 注意自定义注解的时候一定要新建这个类
*
*
* 1。自定义注解必须要添加的注解,@Target
* 1)ElementType.METHOD 这个注解使用在方法上,
* 2)ElementType.TYPE 接口、类、枚举、注解,表示这个类的作用,这里我们把这个类当作注解
* 3)还需要为该注解写一个aop,任何一个方法使用该注解之前先执行aop,把数据源转换为slave
* @author wp
*
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME) //表示运行时该注解生效
public @interface ReadOnlyConnection {
}
有了这个注解是不是说我们把注解加上去就可以切换主从数据源了呢,那明显不可以,下面才是重点,下面我们采用AOP来实现主从的切换
/**
* 切面类
* @author wp
* 当使用readOnlyConnection这个注解的时候就执行这个切面类中的proceed方法,强制切换数据源
*/
@Aspect
@Component
public class ReadOnlyConnectionInterceptor implements Ordered {
public static final Logger LOGGER = LoggerFactory.getLogger(ReadOnlyConnectionInterceptor.class);
/**切入点*/
@Around("@annotation(readOnlyConnection)")
public Object proceed(ProceedingJoinPoint proceedingJoinPoint, ReadOnlyConnection readOnlyConnection) throws Throwable {
try {
LOGGER.info("set database connection to read only");
DataSourceContextHolder.setDateBaseType(DataSourceContextHolder.DataBaseType.SLAVE);
Object result = proceedingJoinPoint.proceed();
return result;
} finally {
DataSourceContextHolder.clearDataBaseType();
LOGGER.info("restore database connection");
}
}
@Override
public int getOrder() {
// TODO Auto-generated method stub
return 0;
}
}
当们使用这个注解的时候我们就强制把数据源切换到从数据源上去,这样不就可以了么,哈哈哈,注意了
@Around这个注解,意思就是环绕,我们看到方法中我们用了try finally ,那么当这个主体方法执行完成后呢,再次调用这个切面,此时执行finally这个方法,并清空ThreadLocal中的值,从而保证线程安全。每个线程之间互不影响。
好了这么大篇幅还是第一次写,写的不好的地方请多多关照,谢谢,
代码上传至了码云Git上,欢迎大家下载
项目下载地址