说明:本文参考http://blog.csdn.net/catoop/article/details/50501664/,在此基础上进行验证与总结。
有时候在服务启动时就要做一些事情,如初始化一些参数和配置信息。在Spring Boot中可以通过实现接口CommandLineRunner来实现。 创建实现接口的类:
@Component public class MyStartupRunner implements CommandLineRunner { @Override public void run(String... strings) throws Exception { System.out.println("****************服务启动执行,执行加载数据等操作***********************"); } }系统启动后会遍历CommandLineRunner接口的实例并运行它们的run方法。如果有多个实例怎么办?哪一个实例先运行?只需要在实例类上添加@Order注解就行,如一个实例添加@Order(value=1),另一个添加@Order(value=2),执行顺序是按value值从小到大。
在代码中打印日志,以方便获取程序执行的一些信息,这几乎是每个程序员都知道的事。初级程序员喜欢用System.out,但这种一般只是将日志信息打印到标准输出上,无法保存,再则会增加资源的消耗。更好的方式是日志系统:Slf4j+logback(对各日志系统的比较在前面的博客-日志组件中有写)。
Spring Boot记录日志只需两步: 1. src/main/resources 下面创建logback.xml 文件(日志文件配置模板可以参考日志组件这篇博客),或者使用在简单的方法在application配置文件中配置。 2. 在代码中打印日志。 日志打印就是普遍使用的方式,在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 spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.jdbc.DriverSpringBoot默认使用org.apache.tomcat.jdbc.pool.DataSource。使用以上配置后,springBoot将使用默认的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接口的匿名类的实例,并且需要实现行映射方法,将返回的行结果映射到实体对象上。 如果想使用其他的连接池如HikariCP(据说是java平台性能最好的连接池),可以在application配置文件中添加一行:spring.datasource.type=com.zaxxer.hikari.HikariDataSource。然后在pom.xml中添加其依赖:
<dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <!-- 版本号可以不用指定,Spring Boot会选用合适的版本 --> </dependency>即可,其他连接池同理。 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 CrudRepository<Score, Integer>{ @Query("select t from Score t ") List<Score> listScore(); }注意,这里接口要继承CrudRepository,并且不需要实现。因为Spring 会自动为我们继承CrudRepository的接口创建实现类(使用动态代理创建接口实例),我们只需要在使用的时候直接使用注解注入即可:
@Resource private IScoreService scoreService;Mybatis连接数据库 (1)配置文件方式 在pom.xml中添加依赖:
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> /*这里只支持1.0.1版本,更高版本不行*/ <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=cn.tonghao.domain有一个需要注意的地方,在controller中调用service,然后在serviceImpl中调用studentMapper时,StudentMapper接口上要加@Component注解,Spring会通过动态代理自动创建该接口的实例。另外在ServiceImpl类上要显式注明bean的名称,否则会报错(按道理讲当接口只有一个实现类时会自动将该实现类作为bean注入,不用显式指定名称,在jdbc连接数据库的例子中就是如此)。 (2)注解方式这里就不说了,需要在接口方法上通过注解来写sql语句。 (3)集成分页插件 Mybatis提供了拦截器接口,我们可以实现自己的拦截器,将其作为一个plugin装入到sqlSessionFactory中。Spring在注入bean的时候,会把所有实现了Mybatis中Interceptor接口的所有类都注入到sqlSessionFactory中,作为plugin存在。这里使用github上的一个分页插件PageHelper,使用@Bean将PageHelper注册为bean即可,注入的时候PageHelper将被注入到SqlSessionFactory作为插件存在。
@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(); }数据源读取与切换类:
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方法上指定数据源就可以从不同的数据源中查询了。