SpringBoot基础(2)

目录:
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记录日志只需两步:

  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?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&amp;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&amp;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/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值