SpringBoot는 즉시 사용 가능한 다양한 플러그인을 제공하기 때문에 오늘날 가장 주류인 Java 웹 개발 프레임워크 중 하나가 되었습니다. Mybatis는 매우 가볍고 사용하기 쉬운 ORM 프레임워크입니다. Redis는 오늘날 웹 개발에서 매우 주류를 이루는 분산 키-값 데이터베이스로 데이터베이스 쿼리 결과를 캐시하는 데 자주 사용됩니다. 이 기사에서는 Spring Boot, Mybatis 및 Redis를 통해 최신 웹 프로젝트를 신속하게 구축하는 방법을 소개하고, 코드 품질을 보장하기 위해 Spring Boot에서 단위 테스트를 우아하게 작성하는 방법도 소개합니다.
이 블로그에서는 SpringBoot를 사용하여 웹 애플리케이션을 빠르게 구축하는 방법과 Mybatis를 ORM 프레임워크로 사용하는 방법을 소개합니다. 성능 향상을 위해 Redis를 Mybatis의 2차 캐시로 사용합니다. 코드를 테스트하기 위해 단위 테스트를 작성하고 H2 인메모리 데이터베이스를 사용하여 테스트 데이터를 생성했습니다. 이 프로젝트를 통해 독자들이 최신 Java 웹 개발의 기술과 모범 사례를 빠르게 익힐 수 있기를 바랍니다.
이 기사의 샘플 코드는 Github에서 다운로드할 수 있습니다: https://github.com/Lovelcp/spring-boot-mybatis-with-redis/tree/master
개발 환경: 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을 초기화해야 합니다. 부트 프로젝트 . Intellij의 Spring 초기화 프로그램을 사용하면 새로운 Spring-Boot 프로젝트를 생성하는 것이 매우 간단해집니다. 먼저 Intellij에서 New Project를 선택합니다:
그런 다음 선택 인터페이스에서 Web, Mybatis, Redis, Mysql, H2를 확인합니다.
새 프로젝트가 성공하면 프로젝트를 볼 수 있습니다. 구조는 아래와 같습니다:
Spring 초기화 프로그램이 자동으로 시작 클래스인 SpringBootMybatisWithRedisApplication을 생성했습니다. 이 클래스의 코드는 매우 간단합니다.
@SpringBootApplication public class SpringBootMybatisWithRedisApplication { public static void main(String[] args) { SpringApplication.run(SpringBootMybatisWithRedisApplication.class, args); } }
@SpringBootApplication 주석은 Spring Boot의 자동 구성 기능을 활성화함을 나타냅니다. 이제 프로젝트 뼈대가 성공적으로 구축되었습니다. 관심 있는 독자는 Intellij를 통해 프로젝트를 시작하여 효과를 확인할 수 있습니다.
새 API 인터페이스 만들기
다음으로 웹 API를 작성해야 합니다. 웹 프로젝트가 판매자의 제품 처리를 담당한다고 가정합니다. 제품 ID를 기반으로 제품 정보를 반환하는 get 인터페이스와 제품 정보를 업데이트하는 put 인터페이스를 제공해야 합니다. 먼저 제품 ID, 제품 이름 및 가격이 포함된 Product 클래스를 정의합니다.
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를 웹 구성 요소로 사용하므로 주석을 통해 인터페이스 클래스를 빠르게 개발할 수 있습니다.
@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: 인터페이스의 URL 주소를 나타냅니다. 클래스에 표시된 @RequestMapping 주석은 클래스 아래의 모든 인터페이스의 URL이 /product로 시작함을 나타냅니다. @GetMapping은 이것이 Get HTTP 인터페이스임을 나타내고, @PutMapping은 이것이 Put HTTP 인터페이스임을 나타냅니다.
@PathVariable, @RequestBody: 매개변수의 매핑 관계를 나타냅니다. /product/123에 액세스하는 Get 요청이 있다고 가정하면 해당 요청은 getProductInfo 메소드에 의해 처리되고 URL의 123이 productId에 매핑됩니다. 마찬가지로 Put 요청인 경우 요청 본문이 newProduct 객체에 매핑됩니다.
여기에서는 인터페이스만 정의했을 뿐, 제품 정보가 데이터베이스에 저장되기 때문에 실제 처리 로직은 아직 완성되지 않았습니다. 다음으로 우리는 mybatis를 프로젝트에 통합하고 데이터베이스와 상호 작용할 것입니다.
Mybatis 통합
데이터 소스 구성
먼저 구성 파일에서 데이터 소스를 구성해야 합니다. 우리는 mysql을 데이터베이스로 사용합니다. 여기서는 구성 파일의 형식으로 yaml을 사용합니다. 리소스 디렉터리에 새 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 구성 클래스를 만들 필요가 없습니다. Spring Boot는 자동으로 구성 파일을 로드합니다. 데이터베이스 연결 풀은 구성 파일의 정보를 기반으로 생성하면 매우 편리합니다.
저자는 구성 파일 형식으로 yaml을 사용할 것을 권장합니다. XML은 장황해 보이고 속성에는 계층 구조가 없습니다. YAML은 이 두 가지의 단점을 보완합니다. 이것이 Spring Boot가 기본적으로 yaml 형식을 지원하는 이유입니다.
Mybatis 구성
我们已经通过Spring Initializer在pom.xml中引入了mybatis-spring-boot-starte库,该库会自动帮我们初始化mybatis。首先我们在application.yml中填写mybatis的相关配置:
# mybatis配置 mybatis: # 配置映射类所在包名 type-aliases-package: com.wooyoo.learning.dao.domain # 配置mapper xml文件所在路径,这里是一个数组 mapper-locations: - mappers/ProductMapper.xml
然后,再在代码中定义ProductMapper类:
@Mapper public interface ProductMapper { Product select( @Param("id") long id); void update(Product product); }
这里,只要我们加上了@Mapper注解,Spring Boot在初始化mybatis时会自动加载该mapper类。
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库,并且提供了开箱即用的redisTemplate和stringTemplate。
将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; } }
讲解一下上述代码中一些关键点:
自己实现的二级缓存,必须要有一个带id的构造函数,否则会报错。
我们使用Spring封装的redisTemplate来操作Redis。网上所有介绍redis做二级缓存的文章都是直接用jedis库,但是笔者认为这样不够Spring Style,而且,redisTemplate封装了底层的实现,未来如果我们不用jedis了,我们可以直接更换底层的库,而不用修改上层的代码。更方便的是,使用redisTemplate,我们不用关心redis连接的释放问题,否则新手很容易忘记释放连接而导致应用卡死。
需要注意的是,这里不能通过autowire的方式引用redisTemplate,因为RedisCache并不是Spring容器里的bean。所以我们需要手动地去调用容器的getBean方法来拿到这个bean,具体的实现方式请参考Github中的代码。
我们采用的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>
d5062ae6bf3ceaffa87bf8be4bed75d6表示开启基于redis的二级缓存,并且在update语句中,我们设置flushCache为true,这样在更新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); } }
在上述测试代码中:
我们首先调用get接口,通过assert语句判断是否得到了预期的对象。此时该product对象会存入redis中。
然后我们调用put接口更新该product对象,此时redis缓存会失效。
最后我们再次调用get接口,判断是否获取到了新的product对象。如果获取到老的对象,说明缓存失效的代码执行失败,代码存在错误,反之则说明我们代码是OK的。
书写单元测试是一个良好的编程习惯。虽然会占用你一定的时间,但是当你日后需要做一些重构工作时,你就会感激过去写过单元测试的自己。
查看测试结果
我们在Intellij中点击执行测试用例,测试结果如下:
显示的是绿色,说明测试用例执行成功了。
本篇文章介绍了如何通过Spring Boot、Mybatis以及Redis快速搭建一个现代化的Web项目,并且同时介绍了如何在Spring Boot下优雅地书写单元测试来保证我们的代码质量。当然这个项目还存在一个问题,那就是mybatis的二级缓存只能通过flush整个DB来实现缓存失效,这个时候可能会把一些不需要失效的缓存也给失效了,所以具有一定的局限性。希望本文能帮助到大家。
相关推荐:
위 내용은 Spring Boot, Mybatis 및 Redis는 최신 웹 프로젝트를 빠르게 구축합니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!