SpringBoot hat sich heute zu einem der am weitesten verbreiteten Java-Webentwicklungs-Frameworks entwickelt, da es eine Vielzahl sofort einsatzbereiter Plug-Ins bietet. Mybatis ist ein sehr leichtes und benutzerfreundliches ORM-Framework. Redis ist heute eine weit verbreitete verteilte Schlüsselwertdatenbank. In der Webentwicklung verwenden wir sie häufig zum Zwischenspeichern von Datenbankabfrageergebnissen. In diesem Artikel erfahren Sie, wie Sie mit Spring Boot, Mybatis und Redis schnell ein modernes Webprojekt erstellen und wie Sie Unit-Tests unter Spring Boot elegant schreiben, um die Qualität unseres Codes sicherzustellen.
In diesem Blog erfahren Sie, wie Sie mit SpringBoot schnell eine Webanwendung erstellen und Mybatis als unser ORM-Framework verwenden. Um die Leistung zu verbessern, verwenden wir Redis als Second-Level-Cache von Mybatis. Um unseren Code zu testen, haben wir Unit-Tests geschrieben und eine H2-In-Memory-Datenbank verwendet, um unsere Testdaten zu generieren. Wir hoffen, dass die Leser durch dieses Projekt schnell die Fähigkeiten und Best Practices der modernen Java-Webentwicklung erlernen können.
Der Beispielcode dieses Artikels kann von Github heruntergeladen werden: https://github.com/Lovelcp/spring-boot-mybatis-with-redis/tree/master
Entwicklungsumgebung: 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
Neues Projekt
Zuerst müssen wir unser Spring-Boot-Projekt initialisieren. Durch den Spring Initializer von Intellij wird es sehr einfach, ein neues Spring-Boot-Projekt zu erstellen. Zuerst wählen wir ein neues Projekt in Intellij aus:
Dann überprüfen wir in der abhängigen Schnittstelle Web, Mybatis, Redis, Mysql, H2:
Nachdem das neue Projekt erfolgreich erstellt wurde, können wir die anfängliche Struktur des Projekts wie unten gezeigt sehen:
Spring Initializer hat es automatisch generiert us Eine Startup-Klasse – SpringBootMybatisWithRedisApplication. Der Code dieser Klasse ist sehr einfach:
@SpringBootApplication public class SpringBootMybatisWithRedisApplication { public static void main(String[] args) { SpringApplication.run(SpringBootMybatisWithRedisApplication.class, args); } }
@SpringBootApplication-Annotation gibt an, die automatische Konfigurationsfunktion von Spring Boot zu aktivieren. Okay, jetzt wurde unser Projektgerüst erfolgreich erstellt. Interessierte Leser können es über Intellij starten, um den Effekt zu sehen.
Erstellen Sie eine neue API-Schnittstelle
Als nächstes müssen wir eine Web-API schreiben. Angenommen, unser Webprojekt ist für die Verarbeitung des Produkts (Produkts) des Händlers verantwortlich. Wir müssen eine Get-Schnittstelle bereitstellen, die Produktinformationen basierend auf der Produkt-ID zurückgibt, und eine Put-Schnittstelle, die Produktinformationen aktualisiert. Zuerst definieren wir die Produktklasse, die Produkt-ID, Produktname und Preis enthält:
public class Product implements Serializable { private static final long serialVersionUID = 1435515995276255188L; private long id; private String name; private long price; // getters setters }
Dann müssen wir die Controller-Klasse definieren. Da Spring Boot Spring MVC intern als Webkomponente verwendet, können wir unsere Schnittstellenklassen schnell durch Annotationen entwickeln:
@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; } }
Wir sind einfach. Lassen Sie uns die Funktionen vorstellen der im obigen Code verwendeten Annotationen:
@RestController: Gibt an, dass die Klasse ein Controller ist und eine Rest-Schnittstelle bereitstellt, d. h. alle Schnittstellenwerte werden im Json-Format zurückgegeben. Diese Annotation ist eigentlich eine kombinierte Annotation von @Controller und @ResponseBody, die uns die Entwicklung der Rest-API erleichtert.
@RequestMapping, @GetMapping, @PutMapping: Stellt die URL-Adresse der Schnittstelle dar. Die in der Klasse markierte Annotation @RequestMapping gibt an, dass die URLs aller Schnittstellen unter der Klasse mit /product beginnen. @GetMapping gibt an, dass es sich um eine Get-HTTP-Schnittstelle handelt, und @PutMapping gibt an, dass es sich um eine Put-HTTP-Schnittstelle handelt.
@PathVariable, @RequestBody: Gibt die Zuordnungsbeziehung von Parametern an. Angenommen, es gibt eine Get-Anfrage, die auf /product/123 zugreift, dann wird die Anfrage von der getProductInfo-Methode verarbeitet und 123 in der URL wird der Produkt-ID zugeordnet. Wenn es sich um eine Put-Anfrage handelt, wird der Anforderungstext entsprechend dem newProduct-Objekt zugeordnet.
Hier haben wir nur die Schnittstelle definiert und die eigentliche Verarbeitungslogik ist noch nicht abgeschlossen, da die Produktinformationen in der Datenbank gespeichert sind. Als nächstes werden wir mybatis in das Projekt integrieren und mit der Datenbank interagieren.
Mybatis integrieren
Datenquelle konfigurieren
Zuerst müssen wir unsere Datenquelle in der Konfigurationsdatei konfigurieren. Wir verwenden MySQL als unsere Datenbank. Hier verwenden wir Yaml als Format unserer Konfigurationsdatei. Wir erstellen eine neue application.yml-Datei im Ressourcenverzeichnis:
spring: # 数据库配置 datasource: url: jdbc:mysql://{your_host}/{your_db} username: {your_username} password: {your_password} driver-class-name: org.gjt.mm.mysql.Driver
Da Spring Boot über die Funktion der automatischen Konfiguration verfügt, müssen wir keine erstellen Eine neue DataSource-Konfigurationsklasse lädt automatisch die Konfigurationsdatei und erstellt einen Datenbankverbindungspool basierend auf den Informationen in der Konfigurationsdatei, was sehr praktisch ist.
Der Autor empfiehlt die Verwendung von Yaml als Konfigurationsdateiformat. XML scheint ausführlich zu sein und Eigenschaften haben keine hierarchische Struktur. YAML gleicht lediglich die Mängel dieser beiden aus. Aus diesem Grund unterstützt Spring Boot standardmäßig das Yaml-Format.
Mybatis konfigurieren
我们已经通过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>
测试
配置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来实现缓存失效,这个时候可能会把一些不需要失效的缓存也给失效了,所以具有一定的局限性。希望本文能帮助到大家。
相关推荐:
Das obige ist der detaillierte Inhalt vonSpring Boot, Mybatis und Redis erstellen schnell moderne Webprojekte. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!