fundo
A Springboot o tornou uma das estruturas de desenvolvimento da Web Java mais convencionais atualmente porque fornece uma variedade de plugins prontos para uso. Mybatis é uma estrutura ORM muito leve e fácil de usar. Redis é um banco de dados de valor-chave distribuído muito mainstream hoje. No desenvolvimento da Web, geralmente o usamos para cache os resultados da consulta do banco de dados.
Este blog introduzirá como criar rapidamente um aplicativo da Web usando o Springboot e usará o Mybatis como nossa estrutura ORM. Para melhorar o desempenho, usamos o Redis como o cache de segundo nível para Mybatis. Para testar nosso código, escrevemos testes de unidade e usamos o banco de dados H2 In Memory para gerar nossos dados de teste. Através deste projeto, esperamos que os leitores possam dominar rapidamente as habilidades e as melhores práticas do desenvolvimento da web java moderno.
O código de exemplo deste artigo pode ser baixado no Github: https://github.com/lovelcp/spring-boot-mybatis-with-redis/tree/master
ambiente
Ambiente de Desenvolvimento: 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
Crie um novo projeto
Primeiro, precisamos inicializar nosso projeto Spring-Boot. Através do Inicializador da Primavera da Intellij, é muito fácil criar um novo projeto de inicialização de primavera. Primeiro, selecionamos um novo projeto em Intellij:
Então, na interface para selecionar dependências, verifique a web, mybatis, redis, mysql, h2:
Depois que o novo projeto for bem -sucedido, podemos ver a estrutura inicial do projeto, como mostrado na figura abaixo:
O Spring Inicializer nos ajudou a gerar automaticamente uma aula de inicialização - SpringBootMyBatisWithRedisApplication. O código desta classe é muito simples:
@SpringbooTApplicationPublic Classe SpringBootMyBatisWithRedisApplication {public static void main (String [] args) {springapplication.run (springbootmybatisWithredisApplication.class, args); }}A anotação de @springbootApplication significa permitir o recurso de configuração automática da inicialização da mola. Ok, nosso esqueleto de projeto foi construído com sucesso para que os leitores interessados possam iniciar os resultados através da Intellij.
Crie uma nova interface de API
Em seguida, escreveremos uma API da Web. Suponha que nossa engenharia da web seja responsável por lidar com os produtos do comerciante (produto). Precisamos fornecer uma interface GET que retorne as informações do produto com base no ID do produto e em uma interface de put que atualiza as informações do produto. Primeiro, definimos a classe de produto, que inclui o ID do produto, o nome do produto e o preço:
Public Class Product implementa serializável {private estático final serialversionuid = 1435515995276255188L; Private Long Id; nome de string privado; preço longo privado; // Getters Setters}Então precisamos definir a classe do controlador. Como a Spring Boot usa o Spring MVC como seu componente da Web internamente, podemos desenvolver rapidamente nossa classe de interface por meio da anotação:
@RestController @requestmapping ("/Product") classe pública ProductController {@getMapping ("/{id}") produto público getProductInfo (@pathvariable ("id") long productId) {// TODO Return null; } @PutMapping ("/{id}") public Product UpdateProductInfo (@PathVariable ("id") Long ProductId, @RequestBody Product NewProduct) {// TODO return null; }}Vamos apresentar brevemente as funções das anotações usadas no código acima:
@RestController: significa que a classe é um controlador e fornece uma interface de repouso, ou seja, os valores de todas as interfaces são retornados no formato JSON. Esta anotação é na verdade uma anotação combinada de @Controller e @ResponseBody, que nos facilita a desenvolver a API REST.
@RequestMapping, @getMapping, @putMapping: representa o endereço URL da interface. A anotação @RequestMapping anotada na classe significa que os URLs de todas as interfaces da classe começam com /produto. @GetMapping significa que esta é uma interface GET HTTP, @putMapp significa que esta é uma interface HTTP de put.
@PathVariable, @Requestbody: representa o relacionamento de mapeamento dos parâmetros. Supondo que um pedido de solicitação GET /produto /123, a solicitação será processada pelo método getProductInfo, onde 123 na URL será mapeado no ProductID. Da mesma forma, se for uma solicitação de put, o corpo solicitado será mapeado para o objeto Newproduct.
Aqui, definimos apenas a interface e a lógica de processamento real ainda não foi concluída, porque as informações do produto estão presentes no banco de dados. Em seguida, integraremos o Mybatis no projeto e interagiremos com o banco de dados.
Integração de Mybatis
Configure a fonte de dados
Primeiro, precisamos configurar nossa fonte de dados no arquivo de configuração. Usamos o MySQL como nosso banco de dados. Aqui usamos a YAML como o formato do nosso arquivo de configuração. Criamos um novo arquivo Application.yml no diretório de recursos:
Spring:# Configuração do banco de dados DataSource: URL: JDBC: mysql: // {your_host}/{your_db} nome de usuário: {your_username} senha: {your_password} driver-class-name: org.gjt.mm.mysql.driverComo a Spring Boot possui o recurso de configuração automática, não precisamos criar uma nova classe Configuração do DataSource. O Spring Boot carregará automaticamente o arquivo de configuração e estabelecerá um pool de conexão de banco de dados com base nas informações do arquivo de configuração, que é muito conveniente.
O autor recomenda que você use o YAML como o formato do arquivo de configuração. O XML parece demorado e as propriedades não têm estrutura hierárquica. Yaml apenas compensa as deficiências de ambos. Esta também é a razão pela qual a Spring Boot suporta o formato YAML por padrão.
Configure mybatis
Introduzimos a biblioteca Mybatis-Spring-Boot-Startt em Pom.xml através do Inicializador da Spring, que nos ajudará automaticamente a inicializar o Mybatis. Primeiro, preenchemos a configuração relevante de Mybatis no Application.yml:
# mybatis configure mybatis: # Configure o nome do pacote em que a classe de mapeamento está localizada tipo-aliases-package: com.wooyoo.learning.dao.Domain # Configure o caminho em que o arquivo Mapper XML está localizado, aqui está uma matriz mapper-locações:-Mapperspappapper.xml
Em seguida, defina a classe ProductMapper no código:
@MapperPublic Interface ProductMapper {Selecionar produto (@param ("id") longo id); Atualização de vazio (produto do produto);}Aqui, desde que adicionemos a anotação @mapper, a Spring Boot carregará automaticamente a classe Mapper ao inicializar o Mybatis.
A maior razão pela qual a Spring Boot é tão popular é o seu recurso de configuração automática. Os desenvolvedores precisam apenas prestar atenção à configuração dos componentes (como informações de conexão com o banco de dados) sem se importar com como inicializar componentes individuais, o que nos permite focar na implementação dos negócios e simplificar o processo de desenvolvimento.
Acessando o banco de dados
Depois de concluir a configuração do MYBATIS, podemos acessar o banco de dados em nossa interface. Introduzimos a classe Mapper através do @Autowired no ProductController e chamamos o método correspondente para implementar a consulta e atualizar operações no produto. Aqui tomamos a interface de consulta como exemplo:
@RestController @requestmapping ("/product") classe pública ProductController {@AUTOWIRED PRODUCTMAPPER ProductMapper; @GetMapping ("/{id}") produto público getProductInfo (@pathvariable ("id") long productId) {return productMapper.select (productId); } // Evite muito tempo e omite o código de updateProductInfo}Em seguida, insira algumas informações do produto no seu MySQL e você poderá executar o projeto para ver se a consulta é bem -sucedida.
Até agora, integramos com sucesso o Mybatis em nosso projeto, adicionando a capacidade de interagir com o banco de dados. Mas isso não é suficiente. Um projeto da Web moderno definitivamente acelerará nossa consulta de banco de dados no cache. Em seguida, introduziremos como integrar cientificamente o Redis ao cache secundário da Mybatis para realizar o cache automático de consultas de banco de dados.
Redis integrado
Configure redis
Assim como acessar um banco de dados, precisamos configurar informações de conexão Redis. Adicione a seguinte configuração ao arquivo Application.yml:
Primavera: Redis: # Redis Database Index (o padrão é 0). Usamos um banco de dados com o índice 3 para evitar conflitos com outros bancos de dados Banco de dados: endereço do servidor 3 # Redis (padrão é localhost) Host: Localhost # Porta Redis (padrão é 6379) Porta: 6379 # Redis Access Password (padrão is NULL) Senha: # Redis Connection Timeout (unidade é Millis Millis) Timeout: Números negativos representam infinito) Max-Active: 8 # Número máximo de conexões inativas (o padrão é 8, números negativos representam infinito) MAX-IDLE: 8 # Número mínimo de conexões ociosas (o padrão é 0, este valor é apenas eficaz) Min-IDLE: 0 # Obtenha a conexão máxima que espera o tempo de conexão (infault é-1, unidade é usina é moagem:
Todos listados acima são configurações comumente usadas, e os leitores podem entender o papel específico de cada item de configuração através de informações de comentários. Desde que introduzimos a biblioteca Spring-Boot-Starter-Data-Redis em Pom.xml, a Spring Boot nos ajudará a carregar automaticamente conexões Redis e classes de configuração específicas
org.springframework.boot.autoconfigure.data.redis.redisautoconfiguration. Através desta classe de configuração, podemos descobrir que a camada subjacente usa a biblioteca JEDIS por padrão e fornece Redistemplate e StringTemplate prontos para fora da caixa.
Use Redis como um cache de nível 2
O princípio do cache secundário de Mybatis não será descrito neste artigo. Os leitores precisam saber apenas que o cache secundário da Mybatis pode armazenar em cache automaticamente as consultas do banco de dados e pode atualizar automaticamente o cache ao atualizar os dados.
A implementação do cache secundário da Mybatis é muito simples. Você só precisa criar uma nova classe para implementar a interface org.apache.ibatis.cache.cache.
Existem cinco métodos para esta interface:
String getId (): o identificador do objeto de operação de cache mybatis. Um mapeador corresponde a um objeto de operação de cache mybatis.
void putObject (chave do objeto, valor do objeto): Equamente os resultados da consulta no cache.
Objeto GetObject (chave do objeto): Obtenha o resultado da consulta em cache do cache.
Objeto RemowObject (chave do objeto): Remova a tecla e o valor correspondentes do cache. Só disparou ao rolar para trás. Geralmente, não precisamos implementá -lo. Para métodos de uso específicos, consulte: org.apache.ibatis.cache.decorators.transactionalcache.
Void Clear (): Limpe o cache quando ocorre uma atualização.
int getSize (): implementação opcional. Retorna o número de caches.
ReadWritelock getReadWritelock (): implementação opcional. Usado para implementar operações de cache atômico.
Em seguida, criamos uma nova classe Rediscache para implementar a interface de cache:
classe pública Rediscache implementa o cache {private estático Logger final Logger = LoggerFactory.getLogger (rediscache.class); Private final ReadWritelock ReadWritelock = new ReentrantreadWritelock (); ID de string final privado; // ID da instância do cache PRIVADO REDISTEMPLATE REDISTEMPLATE; Final estático privado Expire_time_in_minutes = 30; // Redis Expiration Time public Rediscache (string ID) {if (id == null) {lança nova ilegalargumentException ("instâncias de cache requerem um id"); } this.id = id; } @Override public String getId () {return id; } / ** * Coloque o resultado da consulta em redis * * @param key * @param valor * / @Override @suppresswarnings ("desmarcado") public void putObject (chave do objeto, valor do objeto) {redistemplate redistemplate = getredistemplate (); ValueOperations opsForValue = Redistemplate.OpsForValue (); opsforvalue.set (chave, valor, expire_time_in_minutes, timeunit.minutes); Logger.debug ("Coloque o resultado da consulta para redis"); } / ** * Obtenha o resultado da consulta em cache de redis * * @param key * @return * / @Override public Object getObject (chave do objeto) {redistemplate redistemplate = getredistemplate (); ValueOperations opsForValue = Redistemplate.OpsForValue (); Logger.debug ("Obtenha o resultado da consulta em cache de redis"); retornar opsforvalue.get (chave); } / ** * Remova o resultado da consulta em cache de Redis * * @param key * @return * / @override @suppresswarnings ("desmarcado") public Object RemepObject (chave do objeto) {redistemplate redistemplate = getredistemplate (); redistemplate.delete (chave); Logger.debug ("Remova o resultado da consulta em cache de Redis"); retornar nulo; } / ** * Limpa esta instância do cache * / @Override public void clear () {Redistemplate redistemplate = getredistemplate (); RedisteMplate.execute ((Rediscallback) conexão -> {Connection.flushdb (); retorna null;}); Logger.debug ("Limpe todo o resultado da consulta em cache do Redis"); } @Override public int getSize () {return 0; } @Override public readWritelock getReadWritelock () {return readWritelock; } private redistemplate getredistemplate () {if (redistemplate == null) {redistemplate = ApplicationContextholder.getBean ("Redistemplate"); } retornar Redistemplate; }}Deixe -me explicar alguns pontos -chave no código acima:
O cache de segundo nível que você implementa deve ter um construtor com ID, caso contrário, um erro será relatado.
Usamos o RedisteMplate encapsulado da mola para operar o Redis. Todos os artigos on -line que apresentam Redis no cache secundário do nível 2 usam diretamente a biblioteca Jedis, mas o autor acredita que esse não é um estilo de primavera suficiente. Além disso, o Redistemplate encapsula a implementação subjacente. Se não usarmos JEDIS no futuro, podemos substituir diretamente a biblioteca subjacente sem modificar o código superior. O mais conveniente é que, usando o Redistemplate, não precisamos nos preocupar com o lançamento de conexões Redis; caso contrário, será fácil para os iniciantes esquecerem liberar a conexão e fazer com que o aplicativo fique preso.
Deve -se notar que o REDISTEMPLATE não pode ser referenciado através do AutoCire, porque o Rediscache não é um feijão no recipiente da mola. Portanto, precisamos chamar manualmente o método getBean do contêiner para obter este feijão. Para métodos de implementação específicos, consulte o código no GitHub.
O método de serialização de Redis que usamos é a serialização padrão do JDK. Portanto, o objeto de consulta do banco de dados (como a classe do produto) precisa implementar a interface serializável.
Dessa forma, implementamos uma aula de cache elegante, científica e Redis com o estilo da primavera.
Ative o cache do nível 2
Em seguida, precisamos ativar o cache do Nível 2 no ProductMapper.xml:
<? xml versão = "1.0" coding = "utf-8"?> <! namespace = "com.wooyoo.learning.dao.mapper.productmapper"> <!-Ativar cache secundário baseado em redis-> <cache type = "com.wooyoo.learning.util.rediscache"/> <select id = "select" resulttype = "/"> select * de produtos onde id = {> ParameterType = "Product" Flushcache = "True"> Atualizar produtos Definir nome = #{nome}, price = #{price} onde id = #{id} limite 1 </atualize> </mapper><cache type = "com.wooyoo.learning.util.rediscache"/> significa ativar o cache secundário baseado em redis e, na instrução de atualização, definimos o FlushCache como true, para que, ao atualizar as informações do produto, o cache possa ser automaticamente invalidado (essencialmente, o método claro é chamado).
teste
Configurar banco de dados de memória H2
Nesse ponto, concluímos todo o desenvolvimento do código e, em seguida, precisamos escrever o código de teste da unidade para testar a qualidade do nosso código. No processo de desenvolvimento, usamos o banco de dados MySQL e geralmente usamos bancos de dados de memória durante o teste. Aqui usamos o H2 como o banco de dados usado em nosso cenário de teste.
Também é muito simples de usar o H2, você só precisa configurá -lo ao usar o MySQL. No arquivo Application.yml:
--- Spring: Perfis: Teste # Configuração do banco de dados DataSource: URL: JDBC: H2: MEM: Teste Nome de usuário: Raiz Senha: 123456 Nome do driver-classe: Org.H2.Driver Schema: ClassPath: Schema.SQL Data: ClassPath: Data.Sql
Para evitar conflitos com a configuração padrão, usamos-para iniciar um novo parágrafo e usar perfis: teste para indicar que essa é a configuração no ambiente de teste. Em seguida, basta adicionar a anotação @ActiveProfiles (perfis = "teste") à nossa classe de teste para ativar a configuração no ambiente de teste, para que você possa alternar do banco de dados MySQL para o banco de dados H2 com um clique.
Na configuração acima, o esquema.SQL é usado para armazenar nossa declaração de criação de tabela, e o data.sql é usado para armazenar inserir dados. Dessa forma, quando testamos, o H2 lerá esses dois arquivos, inicializará a estrutura e os dados da tabela de que precisamos e o destruirão no final do teste, o que não terá nenhum impacto em nosso banco de dados MySQL. Este é o benefício dos bancos de dados na memória. Além disso, não se esqueça de definir o escopo da dependência do H2 para testar no pom.xml.
Usando a Spring Boot é simples, você pode alternar facilmente bancos de dados em diferentes ambientes sem modificar nenhum código.
Escrevendo código de teste
Como somos inicializados através do Inicializador da Primavera, já temos uma aula de teste - SpringBootMyBatisWithDisaPplicationTests.
A Spring Boot fornece algumas classes de ferramentas que nos facilitam para realizar testes de interface da Web, como o teste de teste. Em seguida, no arquivo de configuração, ajustamos o nível de log para depurar para facilitar a observação dos logs de depuração. O código de teste específico é o seguinte:
@Runwith (springrunner.class) @springboottest (webenvironment = springboottest.webenvironment.random_port) @activeProfiles (perfis = "test") classe pública SpringBootMyBatisWithredImApplicationTests {@localsserVorport private Int Port; @AUTOWIRed Private TestRestTemplate RestTemplate; @Test public void test () {long productId = 1; Produto Produto = RestTemplate.getForObject ("http: // localhost:" + porta + "/product/" + productId, product.class); assertThat (Product.getPrice ()). Isequalto (200); Produto newproduct = new Product (); long newPrice = new Random (). NextLong (); newproduct.setName ("novo nome"); newproduct.setPrice (NewPrice); RestTemplate.put ("http: // localhost:" + porta + "/product/" + productId, newproduct); Produto testProduct = RestTemplate.getForObject ("http: // localhost:" + porta + "/product/" + productId, product.class); assertThat (testProduct.getPrice ()). Isequalto (newPrice); }}No código de teste acima:
Primeiro, chamamos a interface GET e usamos a instrução Assert para determinar se o objeto esperado foi obtido. Neste momento, o objeto do produto será armazenado em Redis.
Em seguida, chamamos a interface PUT para atualizar o objeto do produto, e o cache Redis será invalidado.
Por fim, chamamos a interface GET novamente para determinar se obtivemos um novo objeto de produto. Se um objeto antigo for obtido, significa que o código inválido do cache falhou ao executar e há um erro no código, caso contrário, significa que nosso código está ok.
Escrever testes de unidade é um bom hábito de programação. Embora levará um certo período de tempo para você, quando você precisar fazer algum trabalho de refatoração no futuro, você será grato a si mesmo que escreveu testes de unidade no passado.
Veja os resultados do teste
Clicamos para executar o caso de teste em Intellij, e os resultados dos testes são os seguintes:
O verde é exibido, indicando que o caso de teste foi executado com sucesso.
Resumir
Este artigo apresenta como criar rapidamente um projeto da web moderno com a Spring Boot, Mybatis e Redis e também apresenta como escrever testes de unidade graciosamente sob a inicialização da primavera para garantir a qualidade do nosso código. Obviamente, há outro problema com este projeto, ou seja, o cache de nível 2 da Mybatis só pode ser em cache invalidado, descarregando todo o banco de dados. Neste momento, alguns caches que não precisam ser invalidados também podem ser invalidados, por isso tem certas limitações.