目录:
SpringBoot基础(1)
SpringBoot基础(2)
SpringBoot基础(3)
CommandLineRunner
有时候在服务启动时就要做一些事情,如初始化一些参数和配置信息。在Spring Boot中可以通过实现接口CommandLineRunner来实现。
创建实现接口的类:
@Component
public class MyStartupRunner implements CommandLineRunner {
@Override
public void run(String... strings) throws Exception {
System.out.println("****************服务启动执行,执行加载数据等操作***********************");
}
}
系统启动后会遍历CommandLineRunner接口的实例并运行它们的run方法。如果在有多个CommandLineRunner
的实现类怎么办,哪一个实现类的run方法先运行?只需要在实现类上添加@Order注解就行,如一个实例添加@Order(value=1)
,另一个添加@Order(value=2)
,执行顺序是按value值从小到大。
使用此功能时需要注意,一般服务都是有多服务器实例的,而初始化操作通常只需要执行一次。如果没有其他控制,则初始化操作会在每台服务器实例上都会执行。因此在分布式环境中,需要通过分布式锁来保证初始化操作只执行一次。
日志打印
在代码中打印日志,以方便获取程序执行的一些信息,这几乎是每个程序员都知道的事。初级程序员喜欢用System.out,但这种一般只是将日志信息打印到标准输出上,无法保存,再则会增加资源的消耗。更好的方式是日志系统:Slf4j
+ logback
。
Spring Boot记录日志只需两步:
- src/main/resources 下面创建logback.xml 文件,或者使用最简单的方法在application配置文件中配置。
- 在代码中打印日志。
日志打印就是普遍使用的方式,在Spring Boot中没什么特殊,这个没什么好说的。
数据源
Jdbc连接数据库
在pom中添加依赖:
<!-- MYSQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Spring Boot JDBC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
在application.properties中配置数据库连接参数:
spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=GMT
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
无需配置JDBC连接池类型,
SpringBoot 1.x
默认使用tomcat
连接池,2.x
开始使用HikariCP
连接池。HikariCP的性能远高于c3p0、tomcat等连接池。
假设有一个UserService,其中listUser方法读取user表中的所有user并以List返回,user中只有id和name两个字段。代码如下(这里为方便,直接在service层操作数据库):
@Service
public class UserServiceImpl implements IUserService {
@Resource
private JdbcTemplate jdbcTemplate;
@Override
public List<User> listUser() {
String sql = "SELECT * FROM user";
return (List<User>) jdbcTemplate.query(sql, new RowMapper<User>() {
@Override
public User mapRow(ResultSet rs, int i) throws SQLException {
User u = new User();
u.setId(rs.getInt("id"));
u.setName(rs.getString("name"));
return u;
}
});
}
}
可以看到,这里通过jdbcTemplate来做数据库操作,而jdbcTemplate直接注入就可以。
通过jdbcTemplate.query
方法查询时,第二个参数是一个实现RowMapper接口的匿名类的实例,并且需要实现行映射方法,将返回的行结果映射到实体对象上。
如果想使用其他的连接池,可以在application配置文件中添加一行:spring.datasource.type=xxx
。
JPA连接数据库
将pom.xml中添加的第二个依赖包改为:
<!-- Spring Boot JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
当然如果想同时使用JDBC和JPA,只需要将各自的依赖都添加进即可。定义实体类:
@Entity
@Table(name="score")
public class Score implements Serializable{
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
private int id;
@Column(nullable = false, name="userId")
private int userId;
@Column(nullable = false)
private float sc;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public float getSc() {
return sc;
}
public void setSc(float sc) {
this.sc = sc;
}
}
定义IScoreService接口:
public interface IScoreService extends JpaRepository<Score, Integer>{
@Query("select t from Score t ")
List<Score> listScore();
}
注意,这里接口要继承JpaRepository
,并且不需要实现。因为Spring 会自动为我们继承JpaRepository
的接口创建实现类(使用动态代理创建接口实例),我们只需要在使用的时候直接使用注解注入即可:
@Resource
private IScoreService scoreService;
Mybatis连接数据库
配置文件方式
在pom.xml中添加依赖:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.0.1</version>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>3.4.0</version>
</dependency>
创建接口Mapper和对应的Mapper.xml文件。定义相关方法,注意方法名称要和Mapper.xml文件中的sql的id一致。
@Component
public interface StudentMapper extends MyMapper<Student> {
Student getById(int id);
String getNameById(int id);
}
public interface MyMapper<T> extends Mapper<T>, MySqlMapper<T> {
}
StudentMapper.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="cn.tonghao.mapper.StudentMapper">
<!-- type为实体类Student,包名已经配置,可以直接写类名 -->
<resultMap id="stuMap" type="Student">
<id property="id" column="id" />
<result property="name" column="name" />
<result property="sumScore" column="score_sum" />
<result property="avgScore" column="score_avg" />
<result property="age" column="age" />
</resultMap>
<select id="getById" resultMap="stuMap" resultType="Student">
SELECT *
FROM STUDENT
WHERE ID = #{id}
</select>
<select id="likeName" resultMap="stuMap" parameterType="string" resultType="list">
SELECT *
FROM STUDENT
WHERE NAME LIKE CONCAT('%',#{name},'%')
</select>
<select id="getNameById" resultType="string">
SELECT NAME
FROM STUDENT
WHERE ID = #{id}
</select>
</mapper>
该xml文件放在resources/mapper目录下。
在application中添加:
mybatis.mapper-locations=classpath*:mapper/*Mapper.xml
# 实体类包名
mybatis.type-aliases-package=xx.xxx.xxx
有一个需要注意的地方,在controller中调用service,然后在serviceImpl中调用studentMapper时,StudentMapper接口上要加@Component
注解,Spring会通过动态代理自动创建该接口的实例。
注解方式
注解方式这里就不说了,需要在接口方法上通过注解来写sql语句。比如
@Select("SELECT * FROM user where id = #{id}")
Student getById(int id)
集成分页插件
Mybatis提供了拦截器接口,我们可以实现自己的拦截器,将其作为一个plugin装入到sqlSessionFactory
中。Spring在注入bean的时候,会把所有实现了Mybatis中Interceptor接口的所有类都注入到sqlSessionFactory
中,作为plugin存在。这里使用github上的一个分页插件PageHelper,使用@Bean将PageHelper注册为bean即可,注入的时候PageHelper将被注入到SqlSessionFactory作为插件存在(在5.0及以上的版本中直接使用PageInterceptor)。
@Configuration
public class MyBatisConfiguration {
private static final Logger logger = LoggerFactory.getLogger(MyBatisConfiguration.class);
@Bean
public PageHelper pageHelper() {
logger.info("注册MyBatis分页插件PageHelper");
PageHelper pageHelper = new PageHelper();
Properties p = new Properties();
p.setProperty("offsetAsPageNum", "true");
p.setProperty("rowBoundsWithCount", "true");
p.setProperty("reasonable", "true");
pageHelper.setProperties(p);
return pageHelper;
}
}
调用:
@RequestMapping("/listStudent")
public List<Student> listStudent() {
PageHelper.startPage(1,2);//pageNum,pageSize
return stuService.listStudent();
}
11.多数据源自动切换
数据源读取与切换类:
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
@Aspect
@Order(-1)// 保证该AOP在@Transactional之前执行
@Component
public class DynamicDataSourceAspect {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
@Before("@annotation(ds)")
public void changeDataSource(JoinPoint point, TargetDataSource ds) throws Throwable {
String dsId = ds.name();
if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {
logger.error("数据源[{}]不存在,使用默认数据源 > {}", ds.name(), point.getSignature());
} else {
logger.debug("Use DataSource : {} > {}", ds.name(), point.getSignature());
DynamicDataSourceContextHolder.setDataSourceType(ds.name());
}
}
@After("@annotation(ds)")
public void restoreDataSource(JoinPoint point, TargetDataSource ds) {
logger.debug("Revert DataSource : {} > {}", ds.name(), point.getSignature());
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static List<String> dataSourceIds = new ArrayList<>();
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
public static String getDataSourceType() {
return contextHolder.get();
}
public static void clearDataSourceType() {
contextHolder.remove();
}
/**
* 判断指定DataSrouce当前是否存在
*
* @param dataSourceId
* @return
* @author SHANHY
* @create 2016年1月24日
*/
public static boolean containsDataSource(String dataSourceId){
return dataSourceIds.contains(dataSourceId);
}
}
public class DynamicDataSourceRegister
implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class);
private ConversionService conversionService = new DefaultConversionService();
private PropertyValues dataSourcePropertyValues;
// 如配置文件中未指定数据源类型,使用该默认值
private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource";
// private static final Object DATASOURCE_TYPE_DEFAULT =
// "com.zaxxer.hikari.HikariDataSource";
// 数据源
private DataSource defaultDataSource;
private Map<String, DataSource> customDataSources = new HashMap<>();
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
// 将主数据源添加到更多数据源中
targetDataSources.put("dataSource", defaultDataSource);
DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
// 添加更多数据源
targetDataSources.putAll(customDataSources);
for (String key : customDataSources.keySet()) {
DynamicDataSourceContextHolder.dataSourceIds.add(key);
}
// 创建DynamicDataSource
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(DynamicDataSource.class);
beanDefinition.setSynthetic(true);
MutablePropertyValues mpv = beanDefinition.getPropertyValues();
mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
mpv.addPropertyValue("targetDataSources", targetDataSources);
registry.registerBeanDefinition("dataSource", beanDefinition);
logger.info("Dynamic DataSource Registry");
}
/**
* 创建DataSource
* @return
* @author SHANHY
* @create 2016年1月24日
*/
@SuppressWarnings("unchecked")
public DataSource buildDataSource(Map<String, Object> dsMap) {
try {
Object type = dsMap.get("type");
if (type == null)
type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource
Class<? extends DataSource> dataSourceType;
dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);
String driverClassName = dsMap.get("driver-class-name").toString();
String url = dsMap.get("url").toString();
String username = dsMap.get("username").toString();
String password = dsMap.get("password").toString();
DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url)
.username(username).password(password).type(dataSourceType);
return factory.build();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
/**
* 加载多数据源配置
*/
@Override
public void setEnvironment(Environment env) {
initDefaultDataSource(env);
initCustomDataSources(env);
}
/**
* 初始化主数据源
*
* @author SHANHY
* @create 2016年1月24日
*/
private void initDefaultDataSource(Environment env) {
// 读取主数据源
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "spring.datasource.");
Map<String, Object> dsMap = new HashMap<>();
dsMap.put("type", propertyResolver.getProperty("type"));
dsMap.put("driver-class-name", propertyResolver.getProperty("driver-class-name"));
dsMap.put("url", propertyResolver.getProperty("url"));
dsMap.put("username", propertyResolver.getProperty("username"));
dsMap.put("password", propertyResolver.getProperty("password"));
defaultDataSource = buildDataSource(dsMap);
dataBinder(defaultDataSource, env);
}
/**
* 为DataSource绑定更多数据
*
* @param dataSource
* @param env
* @author SHANHY
* @create 2016年1月25日
*/
private void dataBinder(DataSource dataSource, Environment env){
RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);
//dataBinder.setValidator(new LocalValidatorFactory().run(this.applicationContext));
dataBinder.setConversionService(conversionService);
dataBinder.setIgnoreNestedProperties(false);//false
dataBinder.setIgnoreInvalidFields(false);//false
dataBinder.setIgnoreUnknownFields(true);//true
if(dataSourcePropertyValues == null){
Map<String, Object> rpr = new RelaxedPropertyResolver(env, "spring.datasource").getSubProperties(".");
Map<String, Object> values = new HashMap<>(rpr);
// 排除已经设置的属性
values.remove("type");
values.remove("driver-class-name");
values.remove("url");
values.remove("username");
values.remove("password");
dataSourcePropertyValues = new MutablePropertyValues(values);
}
dataBinder.bind(dataSourcePropertyValues);
}
/**
* 初始化更多数据源
*
* @author SHANHY
* @create 2016年1月24日
*/
private void initCustomDataSources(Environment env) {
// 读取配置文件获取更多数据源,也可以通过defaultDataSource读取数据库获取更多数据源
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "custom.datasource.");
String dsPrefixs = propertyResolver.getProperty("names");
for (String dsPrefix : dsPrefixs.split(",")) {// 多个数据源
Map<String, Object> dsMap = propertyResolver.getSubProperties(dsPrefix + ".");
DataSource ds = buildDataSource(dsMap);
customDataSources.put(dsPrefix, ds);
dataBinder(ds, env);
}
}
}
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String name();
}
以上5个类为数据源自动切换的基础类,然后在SpringBootSampleApplication上添加注解:@Import({DynamicDataSourceRegister.class})
。在application.xml中添加配置:
# 更多数据源
custom.datasource.names=ds1,ds2
custom.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource
custom.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
custom.datasource.ds1.url=jdbc:mysql://localhost:3306/test1?characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
custom.datasource.ds1.username=root
custom.datasource.ds1.password=root
custom.datasource.ds2.type=com.zaxxer.hikari.HikariDataSource
custom.datasource.ds2.driver-class-name=com.mysql.jdbc.Driver
custom.datasource.ds2.url=jdbc:mysql:// localhost:3306/test2?characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
custom.datasource.ds2.username=root
custom.datasource.ds2.password=root
仍然以studentService为例:
@Override
@TargetDataSource(name="ds1")
public List<Student> likeName(String name) {
return studentMapper.likeName(name);
}
该方法对应sql为:
<select id="likeName" resultMap="stuMap" parameterType="string" resultType="list">
SELECT *
FROM STUDENT
WHERE NAME like CONCAT('%',#{name},'%')
</select>
在likeName方法上指定数据源就可以从不同的数据源中查询了。
参考资料
[1]. http://blog.csdn.net/catoop/article/details/50501664/