澳门新浦京8455comSpring Boot + Mybatis + Redis二级缓存开发指南

澳门新浦京8455com 4

背景

Spring-Boot因其提供了各样开箱即用的插件,使得它成为了现行反革命特别主流的Java
Web开采框架之一。Mybatis是二个超轻量好用的ORM框架。Redis是当今十分主流的布满式key-value型数据库,在web开荒中,大家常用它来缓存数据库的查询结果。

本篇博客将介绍怎样使用Spring-Boot飞快搭建二个Web应用,而且利用Mybatis作为大家的ORM框架。为了进步质量,我们将Redis作为Mybatis的二级缓存。为了测试大家的代码,大家编辑了单元测量检验,而且用H2内部存款和储蓄器数据库来生成大家的测量试验数据。通过该项目,大家盼望读者能够快捷调整今世化Java
Web开荒的技术以至顶尖实行。

正文的亲自去做代码可在Github中下载:

环境

  • 开拓情形:mac 10.11
  • ide:Intellij 2017.1
  • jdk:1.8
  • Spring-Boot:1.5.3.RELEASE
  • Redis:3.2.9
  • Mysql:5.7

Spring-Boot

新建项目

率先,大家必要早先化大家的Spring-Boot工程。通过英特尔lij的Spring
Initializer,新建二个Spring-Boot工程变得不行大致。首先大家在英特尔lij中选取New一个Project:

澳门新浦京8455com 1

下一场在甄选正视的分界面,勾选Web、Mybatis、Redis、Mysql、H2:

澳门新浦京8455com 2

新建筑工程程中标以后,大家能够看出项指标上马布局如下图所示:

澳门新浦京8455com 3

Spring
Initializer已经帮我们自动生成了二个开发银行类——SpringBootMybatisWithRedisApplication。该类的代码十二分简易:

@SpringBootApplication
public class SpringBootMybatisWithRedisApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootMybatisWithRedisApplication.class, args);
    }
}

@SpringBootApplication讲解表示启用Spring
Boot的电动配置特点。好了,至此大家的门类骨架业已搭建设成功,感兴趣的读者可以经过AMDlij运行看看效果。

新建API接口

接下去,大家要编写Web
API。假诺大家的Web工程肩负处理集团的制品(Product)。我们必要提供依据product
id重返product音讯的get接口和翻新product音讯的put接口。首先我们定义Product类,该类满含产物id,产物名称name以致价格price:

public class Product implements Serializable {
    private static final long serialVersionUID = 1435515995276255188L;
    private long id;
    private String name;
    private long price;
    // getters setters
}

然后大家供给定义Controller类。由于Spring Boot内部使用Spring
MVC作为它的Web组件,所以我们得以经过评释的办法急速支付大家的接口类:

@RestController
@RequestMapping("/product")
public class ProductController {
    @GetMapping("/{id}")
    public Product getProductInfo(
            @PathVariable("id")
                    Long productId) {
        // TODO
        return null;
    }
    @PutMapping("/{id}")
    public Product updateProductInfo(
            @PathVariable("id")
                    Long productId,
            @RequestBody
                    Product newProduct) {
        // TODO
        return null;
    }
}

大家大约介绍一下上述代码中所用到的表明的作用:

  • @RestController:表示该类为Controller,並且提供Rest接口,即具有接口的值以Json格式重临。该注脚其实是@Controller@ResponseBody的重新组合注脚,便于大家付出REST
    API。
  • @RequestMapping@GetMapping@PutMapping:表示接口的UQashqaiL地址。标明在类上的@RequestMapping注活血示该类下的持有接口的U纳瓦拉L都是/product开头。@GetMapping代表那是三个Get
    HTTP接口,@PutMapping表示那是多少个Put HTTP接口。
  • @PathVariable@RequestBody:表示参数的映照关系。就算有个Get乞求访谈的是/product/123,那么该乞求会由getProductInfo措施管理,此中U凯雷德L里的123会被映射到productId中。同理,假诺是Put诉求的话,央求的body会被映射到newProduct对象中。

此间我们只定义了接口,实际的拍卖逻辑还未有到位,因为product的信息都设有数据库中。接下来大家就要品种中集成mybatis,並且与数据库做交互作用。

集成Mybatis

配置数据源

首先大家要求在铺排文件中配备我们的数据源。大家应用mysql作为我们的数据库。这里大家选取yaml作为大家布署文件的格式。大家在resources目录下新建application.yml文件:

spring:
  # 数据库配置
  datasource:
    url: jdbc:mysql://{your_host}/{your_db}
    username: {your_username}
    password: {your_password}
    driver-class-name: org.gjt.mm.mysql.Driver

由于Spring
Boot具备电动配置的特色,我们毫不新建四个DataSource的配备类,Sping
Boot会自动加载配置文件同期依照布置文件的消息建构数据库的连接池,十二分便民。

小编推荐大家利用yaml作为配置文件的格式。xml显得冗长,properties未有层级构造,yaml适逢其会弥补了这两个的短处。那也是Spring
Boot暗中同意就帮助yaml格式的从头至尾的经过。

配置Mybatis

咱俩早已由此Spring
Initializer在pom.xml中引入了mybatis-spring-boot-starte库,该库会自动帮大家伊始化mybatis。首先我们在application.yml中钦命mybatis的配备文件:

# mybatis配置
mybatis:
  config-location: classpath:mybatis-config.xml

接下来大家在resources目录下新建mybatis-config.xml文本。在安插文件中,咱们需求内定Product类以至mapper文件的路线:

<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <typeAliases>
        <package name="com.wooyoo.learning.dao.domain"/>
    </typeAliases>
    <mappers>
        <mapper resource="mappers/ProductMapper.xml"/>
    </mappers>
</configuration>

接下去,大家再在resourses目录下新建mappers目录,並且新建ProductMapper.xml文本,编写操作products表的SQL语句(该文件的源委请仿照效法小编在前文贴的Github仓库地址)。最终,再在代码中定义ProductMapper类:

@Mapper
public interface ProductMapper {
    Product select(
            @Param("id")
                    long id);
    void update(Product product);
}

Spring
Boot之所以这么流行,最大的原由是它自动配置的性状。开垦者只必要关怀组件的布局(比方数据库的连接新闻),而无需关心怎么样早先化各类零器件,那使得我们能够三月不知肉味专心于业务的落到实处,简化开采流程。

做客数据库

成功了Mybatis的布署之后,大家就足以在我们的接口中访谈数据库了。我们在ProductController下通过@Autowired引进mapper类,何况调用对应的不二秘籍完成对product的询问和换代操作,这里大家以询问接口为例:

@RestController
@RequestMapping("/product")
public class ProductController {
    @Autowired
    private ProductMapper productMapper;
    @GetMapping("/{id}")
    public Product getProductInfo(
            @PathVariable("id")
                    Long productId) {
        return productMapper.select(productId);
    }
    // 避免篇幅过长,省略updateProductInfo的代码
}

下一场在您的mysql中插入几条product的音讯,就能够运作该品种看看是不是可以查询成功了。

从那之后,大家早已打响地在等级次序中合拢了Mybatis,扩大了与数据库人机联作的技巧。可是那还远远不够,叁个现代化的Web项目,分明会上缓存加速大家的数据库查询。接下来,将介绍如何科学地将Redis集成到Mybatis的二级缓存中,达成数据库查询的自行缓存。

集成Redis

配置Redis

同访谈数据库同样,我们必要配置Redis的接连几日信息。在application.yml文件中追加如下配置:

spring:
  redis:
    # redis数据库索引(默认为0),我们使用索引为3的数据库,避免和其他数据库冲突
    database: 3
    # redis服务器地址(默认为localhost)
    host: localhost
    # redis端口(默认为6379)
    port: 6379
    # redis访问密码(默认为空)
    password:
    # redis连接超时时间(单位为毫秒)
    timeout: 0
    # redis连接池配置
    pool:
      # 最大可用连接数(默认为8,负数表示无限)
      max-active: 8
      # 最大空闲连接数(默认为8,负数表示无限)
      max-idle: 8
      # 最小空闲连接数(默认为0,该值只有为正数才有作用)
      min-idle: 0
      # 从连接池中获取连接最大等待时间(默认为-1,单位为毫秒,负数表示无限)
      max-wait: -1

上述列出的都为常用配置,读者能够因此注释消息摸底种种配置项的切切实时效果。由于大家在pom.xml中早已引进了spring-boot-starter-data-redis库,所以Spring
Boot会帮大家自行加载Redis的连接,具体的配置类org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration。通过该配置类,大家能够窥见底层暗中认可使用Jedis库,并且提供了开箱即用的redisTemplatestringTemplate

将Redis作为二级缓存

Mybatis的二级缓存原理本文不再赘述,读者只要了然,Mybatis的二级缓存能够自行地对数据库的询问做缓存,况兼能够在更新数据时同不经常候自动地立异缓存。

落实Mybatis的二级缓存异常粗略,只须要新建一个类落成org.apache.ibatis.cache.Cache接口就能够。

该接口共有以下四个艺术:

  • String getId():mybatis缓存操作对象的标记符。叁个mapper对应三个mybatis的缓存操作对象。
  • void putObject(Object key, Object value):将查询结果塞入缓存。
  • Object getObject(Object key):从缓存中获取被缓存的查询结果。
  • Object removeObject(Object key):从缓存中剔除相应的key、value。唯有在回滚时接触。平日大家也能够绝不完成,具体行使办法请参见:org.apache.ibatis.cache.decorators.TransactionalCache
  • void clear():产生更新时,打消缓存。
  • int getSize():可选达成。重回缓存的数码。
  • ReadWriteLock getReadWriteLock():可选完成。用于贯彻原子性的缓存操作。

接下去,大家新建RedisCache类,实现Cache接口:

public class RedisCache implements Cache {
    private static final Logger logger = LoggerFactory.getLogger(RedisCache.class);
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private final String id; // cache instance id
    private RedisTemplate redisTemplate;
    private static final long EXPIRE_TIME_IN_MINUTES = 30; // redis过期时间
    public RedisCache(String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        this.id = id;
    }
    @Override
    public String getId() {
        return id;
    }
    /**
     * Put query result to redis
     *
     * @param key
     * @param value
     */
    @Override
    @SuppressWarnings("unchecked")
    public void putObject(Object key, Object value) {
        RedisTemplate redisTemplate = getRedisTemplate();
        ValueOperations opsForValue = redisTemplate.opsForValue();
        opsForValue.set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
        logger.debug("Put query result to redis");
    }
    /**
     * Get cached query result from redis
     *
     * @param key
     * @return
     */
    @Override
    public Object getObject(Object key) {
        RedisTemplate redisTemplate = getRedisTemplate();
        ValueOperations opsForValue = redisTemplate.opsForValue();
        logger.debug("Get cached query result from redis");
        return opsForValue.get(key);
    }
    /**
     * Remove cached query result from redis
     *
     * @param key
     * @return
     */
    @Override
    @SuppressWarnings("unchecked")
    public Object removeObject(Object key) {
        RedisTemplate redisTemplate = getRedisTemplate();
        redisTemplate.delete(key);
        logger.debug("Remove cached query result from redis");
        return null;
    }
    /**
     * Clears this cache instance
     */
    @Override
    public void clear() {
        RedisTemplate redisTemplate = getRedisTemplate();
        redisTemplate.execute((RedisCallback) connection -> {
            connection.flushDb();
            return null;
        });
        logger.debug("Clear all the cached query result from redis");
    }
    @Override
    public int getSize() {
        return 0;
    }
    @Override
    public ReadWriteLock getReadWriteLock() {
        return readWriteLock;
    }
    private RedisTemplate getRedisTemplate() {
        if (redisTemplate == null) {
            redisTemplate = ApplicationContextHolder.getBean("redisTemplate");
        }
        return redisTemplate;
    }
}

教师一下上述代码中一些关键点

  1. 慈详完成的二级缓存,必定要有二个带id的结构函数,不然会报错。
  2. 大家接收Spring封装的redisTemplate来操作Redis。网络具备介绍redis做二级缓存的作品都是一贯用jedis库,可是小编认为那样远远不够Spring
    Style,而且,redisTemplate打包了底部的得以达成,未来一旦大家绝不jedis了,大家得以直接改换底层的库,而不用校正上层的代码。更实惠的是,使用redisTemplate,大家不要关注redis连接的放走难题,不然生手相当轻易忘记释放连接而形成应用卡死。
  3. 亟待在乎的是,这里无法透过autowire的方法引用redisTemplate,因为RedisCache实际不是Spring容器里的bean。所以我们必要手动地去调用容器的getBean措施来得到这么些bean,具体的兑现方式请参见Github中的代码。
  4. 笔者们利用的redis类别化格局是暗中认可的jdk种类化。所以数据库的询问对象(举例Product类)供给达成Serializable接口。

这么,我们就落到实处了三个平淡的、科学的同一时候有所Spring Style的Redis缓存类。

张开二级缓存

接下去,大家须求在ProductMapper.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="com.wooyoo.learning.dao.mapper.ProductMapper">
    <!-- 开启基于redis的二级缓存 -->
    <cache type="com.wooyoo.learning.util.RedisCache"/>
    <select id="select" resultType="Product">
        SELECT * FROM products WHERE id = #{id} LIMIT 1
    </select>
    <update id="update" parameterType="Product" flushCache="true">
        UPDATE products SET name = #{name}, price = #{price} WHERE id = #{id} LIMIT 1
    </update>
</mapper>

<cache type="com.wooyoo.learning.util.RedisCache"/>意味着开启基于redis的二级缓存,并且在update语句中,大家设置flushCachetrue,那样在修改product音讯时,可以活动失效缓存(本质上调用的是clear方法)。

测试

布局H2内部存款和储蓄器数据库

于今停止我们曾经实现了富有代码的支付,接下去大家须求书写单元测量试验代码来测验我们代码的质量。大家刚刚开辟的经过中央银行使的是mysql数据库,而相符大家在测验时经常利用的是内部存款和储蓄器数据库。这里大家采用H2作为咱们测验场景中使用的数据库。

要动用H2也很简短,只须要跟使用mysql时安顿一下就能够。在application.yml文件中:

---
spring:
  profiles: test
  # 数据库配置
  datasource:
    url: jdbc:h2:mem:test
    username: root
    password: 123456
    driver-class-name: org.h2.Driver
    schema: classpath:schema.sql
    data: classpath:data.sql

为了防止和暗许的布署冲突,大家用---另起一段,况兼用profiles: test表明那是test景况下的布署。然后若是在大家的测验类中加多@ActiveProfiles(profiles = "test")批注来启用test情况下的安插,那样就会一键从mysql数据库切换来h2数据库。

在上述配置中,schema.sql用于寄存我们的建表语句,data.sql用于存放insert的数额。那样当大家测量试验时,h2就能够读取那七个文件,初步化大家所须要的表布局以至数额,然后在测量检验结束时销毁,不会对我们的mysql数据库发生其余影响。那正是内部存款和储蓄器数据库的好处。此外,别忘了在pom.xml准将h2的依赖的scope设置为test。

行使Spring
Boot正是这么轻易,没有必要修正任何代码,轻巧达成数据库在差异条件下的切换。

编纂测量检验代码

因为我们是通过Spring
Initializer早先化的档期的顺序,所以已经有了叁个测量检验类——SpringBootMybatisWithRedisApplicationTests

Spring
Boot提供了一部分利于大家举办Web接口测量检验的工具类,比如TestRestTemplate。然后在配备文件中大家将log等第调成DEBUG,方便观察调试日志。具体的测量检验代码如下:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles(profiles = "test")
public class SpringBootMybatisWithRedisApplicationTests {
    @LocalServerPort
    private int port;
    @Autowired
    private TestRestTemplate restTemplate;
    @Test
    public void test() {
        long productId = 1;
        Product product = restTemplate.getForObject("http://localhost:" + port + "/product/" + productId, Product.class);
        assertThat(product.getPrice()).isEqualTo(200);
        Product newProduct = new Product();
        long newPrice = new Random().nextLong();
        newProduct.setName("new name");
        newProduct.setPrice(newPrice);
        restTemplate.put("http://localhost:" + port + "/product/" + productId, newProduct);
        Product testProduct = restTemplate.getForObject("http://localhost:" + port + "/product/" + productId, Product.class);
        assertThat(testProduct.getPrice()).isEqualTo(newPrice);
    }
}

在上述测验代码中:

  1. 咱俩先是调用get接口,通过assert语句决断是还是不是获得了预想的目的。此时该product对象会存入redis中。
  2. 接下来大家调用put接口更新该product对象,那时候redis缓存会失效。
  3. 终极大家再一次调用get接口,推断是或不是取得到了新的product对象。若是取获得老的靶子,表达缓存失效的代码推行失利,代码存在错误,反之则证实大家代码是OK的。

书写单元测验是三个大好的编制程序习于旧贯。就算会占领你早晚的光阴,不过当您之后内需做一些重构专门的工作时,你就能够领情过去写过单元测量检验的友好。

查阅测验结果

小编们在英特尔lij中式茶食击实施测量检验用例,测验结果如下:

澳门新浦京8455com 4

真棒,显示的是彩虹色,表达测验用例试行成功了。

总结

本篇文章介绍了什么通过Spring
Boot、Mybatis以至Redis神速搭建一个今世化的Web项目,况兼同一时候介绍了哪些在Spring
Boot下高雅地书写单元测量检验来作保我们的代码品质。当然这几个类型还留存一个难题,那正是mybatis的二级缓存只好通过flush整个DB来落实缓存失效,当时大概会把一些没有必要失效的缓存也给失效了,所以具有自然的局限性。

可望由此本篇文章,可以给读者带给一些拿走和启迪。有任何的理念或许提议请在本文下方商量。

参照他事他说加以侦察资料

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图