SpringBoot基础(2)

来源:互联网 发布:参加学术会议 知乎 编辑:程序博客网 时间:2024/06/10 21:39

说明:本文参考http://blog.csdn.net/catoop/article/details/50501664/,在此基础上进行验证与总结。

8.CommandLineRunner

有时候在服务启动时就要做一些事情,如初始化一些参数和配置信息。在Spring Boot中可以通过实现接口CommandLineRunner来实现。
创建实现接口的类:

@Componentpublic 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值从小到大。

9.日志打印

在代码中打印日志,以方便获取程序执行的一些信息,这几乎是每个程序员都知道的事。初级程序员喜欢用System.out,但这种一般只是将日志信息打印到标准输出上,无法保存,再则会增加资源的消耗。更好的方式是日志系统:Slf4j+logback(对各日志系统的比较在前面的博客-日志组件中有写)。

Spring Boot记录日志只需两步:
1. src/main/resources 下面创建logback.xml 文件(日志文件配置模板可以参考日志组件这篇博客),或者使用在简单的方法在application配置文件中配置。
2. 在代码中打印日志。
日志打印就是普遍使用的方式,在Spring Boot中没什么特殊,这个没什么好说的。

10.数据源

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/testspring.datasource.username=rootspring.datasource.password=rootspring.datasource.driver-class-name=com.mysql.jdbc.Driver

SpringBoot默认使用org.apache.tomcat.jdbc.pool.DataSource。使用以上配置后,springBoot将使用默认的tomcat的连接池,并自动设置好数据库连接参数。
假设有一个UserService,其中listUser方法读取user表中的所有user并以List返回,user中只有id和name两个字段。代码如下(这里为方便,直接在service层操作数据库):

@Servicepublic 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的接口创建实现类(使用动态代理创建接口实例),我们只需要在使用的时候直接使用注解注入即可:

@Resourceprivate 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一致。

@Componentpublic 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作为插件存在。

@Configurationpublic 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之前执行@Componentpublic 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)@Documentedpublic @interface TargetDataSource {   String name();}

以上5个类为数据源自动切换的基础类,然后在SpringBootSampleApplication上添加注解:@Import({DynamicDataSourceRegister.class})。在application.xml中添加配置:

# 更多数据源custom.datasource.names=ds1,ds2custom.datasource.ds1.type=com.zaxxer.hikari.HikariDataSourcecustom.datasource.ds1.driver-class-name=com.mysql.jdbc.Drivercustom.datasource.ds1.url=jdbc:mysql://localhost:3306/test1?characterEncoding=UTF-8&amp;zeroDateTimeBehavior=convertToNullcustom.datasource.ds1.username=rootcustom.datasource.ds1.password=rootcustom.datasource.ds2.type=com.zaxxer.hikari.HikariDataSourcecustom.datasource.ds2.driver-class-name=com.mysql.jdbc.Drivercustom.datasource.ds2.url=jdbc:mysql:// localhost:3306/test2?characterEncoding=UTF-8&amp;zeroDateTimeBehavior=convertToNullcustom.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方法上指定数据源就可以从不同的数据源中查询了。

原创粉丝点击