latar belakang
Springboot telah menjadikannya salah satu kerangka kerja pengembangan web Java yang paling utama saat ini karena menyediakan berbagai plugin out-of-the-box. Mybatis adalah kerangka kerja ORM yang sangat ringan dan mudah digunakan. Redis adalah database nilai kunci terdistribusi yang sangat utama saat ini. Dalam pengembangan Web, kami sering menggunakannya untuk cache hasil kueri database.
Blog ini akan memperkenalkan cara membangun aplikasi web dengan cepat menggunakan springboot dan menggunakan mybatis sebagai kerangka kerja ORM kami. Untuk meningkatkan kinerja, kami menggunakan Redis sebagai cache tingkat kedua untuk Mybatis. Untuk menguji kode kami, kami menulis tes unit dan menggunakan database dalam memori H2 untuk menghasilkan data uji kami. Melalui proyek ini, kami berharap pembaca dapat dengan cepat menguasai keterampilan dan praktik terbaik pengembangan web Java modern.
Kode sampel untuk artikel ini dapat diunduh di github: https://github.com/lovelcp/spring-boot-mybatis-with-redis/tree/master
lingkungan
Lingkungan Pengembangan: 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
Buat proyek baru
Pertama, kita perlu menginisialisasi proyek boot-boot kami. Melalui Intellij's Spring Initializer, sangat mudah untuk membuat proyek boot-boot baru. Pertama, kami memilih proyek baru di Intellij:
Kemudian di antarmuka untuk memilih dependensi, memeriksa web, mybatis, redis, mysql, h2:
Setelah proyek baru berhasil, kita dapat melihat struktur awal proyek seperti yang ditunjukkan pada gambar di bawah ini:
Spring Initializer telah membantu kami secara otomatis menghasilkan kelas startup - SpringbootmybatiswithRedisApplication. Kode kelas ini sangat sederhana:
@SpringbootApplicationPublic Class Public SpringboTMyBatisWithRedisApplication {public static void main (string [] args) {springApplication.run (springbootmybatiswithredisapplication.class, args); }}Anotasi @springbootApplication berarti mengaktifkan fitur konfigurasi otomatis dari Spring Boot. Oke, kerangka proyek kami telah berhasil dibangun sehingga pembaca yang tertarik dapat memulai hasilnya melalui IntelliJ.
Buat antarmuka API baru
Selanjutnya, kami akan menulis API web. Misalkan rekayasa web kami bertanggung jawab untuk menangani produk pedagang (produk). Kami perlu menyediakan antarmuka GET yang mengembalikan informasi produk berdasarkan ID produk dan antarmuka put yang memperbarui informasi produk. Pertama kami mendefinisikan kelas produk, yang mencakup ID produk, nama produk, dan harga:
Produk kelas publik mengimplementasikan serializable {private static final long serialversionuid = 1435515955276255188l; ID Panjang Pribadi; nama string pribadi; harga panjang pribadi; // getters setter}Maka kita perlu mendefinisikan kelas pengontrol. Karena Spring Boot menggunakan Spring MVC sebagai komponen webnya secara internal, kami dapat dengan cepat mengembangkan kelas antarmuka kami melalui anotasi:
@Restcontroller @requestMapping ("/Product") Public Class ProductController {@GetMapping ("/{id}") Produk publik getProductInfo (@pathvariable ("id") long productId) {// todo return null; } @PutMapping ("/{id}") Public Product UpdateProductInfo (@PathVariable ("ID") Long ProductId, @RequestBody Product NewProduct) {// TODO Return null; }}Mari kita perkenalkan secara singkat fungsi -fungsi anotasi yang digunakan dalam kode di atas:
@RestController: berarti bahwa kelas adalah pengontrol dan menyediakan antarmuka REST, yaitu nilai semua antarmuka dikembalikan dalam format JSON. Anotasi ini sebenarnya adalah anotasi kombinasi dari @Controller dan @ResponseBody, yang memfasilitasi kami untuk mengembangkan API REST.
@RequestMapping, @GetMapping, @PutMapping: mewakili alamat URL antarmuka. Anotasi @RequestMapping yang dijelaskan pada kelas berarti URL dari semua antarmuka di bawah kelas mulai dengan /produk. @GetMapping berarti ini adalah antarmuka GET HTTP, @PutMapping berarti ini adalah antarmuka HTTP.
@PathVariable, @RequestBody: mewakili hubungan pemetaan parameter. Dengan asumsi bahwa permintaan GET mengakses /Produk /123, permintaan akan diproses dengan metode GetProductInfo, di mana 123 dalam URL akan dipetakan ke ProductID. Demikian pula, jika itu adalah permintaan put, badan yang diminta akan dipetakan ke objek NewProduct.
Di sini kami hanya mendefinisikan antarmuka, dan logika pemrosesan yang sebenarnya belum selesai, karena informasi produk ada dalam database. Selanjutnya kita akan mengintegrasikan mybatis dalam proyek dan berinteraksi dengan database.
Integrasi mybatis
Konfigurasikan sumber data
Pertama, kita perlu mengonfigurasi sumber data kita di file konfigurasi. Kami menggunakan MySQL sebagai database kami. Di sini kami menggunakan YAML sebagai format file konfigurasi kami. Kami membuat file application.yml baru di direktori sumber daya:
Spring:# Database Konfigurasi DataSource: url: jdbc: mysql: // {your_host}/{your_db} nama pengguna: {your_username} kata sandi: {your_password} driver-class-name: org.gjt.mmymyql.driverKarena Spring Boot memiliki fitur konfigurasi otomatis, kami tidak perlu membuat kelas konfigurasi DataSource baru. Spring Boot akan secara otomatis memuat file konfigurasi dan membuat kumpulan koneksi basis data berdasarkan informasi file konfigurasi, yang sangat nyaman.
Penulis merekomendasikan agar Anda menggunakan YAML sebagai format file konfigurasi. XML terlihat panjang dan sifat tidak memiliki struktur hierarkis. Yaml hanya menebus kekurangan keduanya. Ini juga alasan mengapa Spring Boot mendukung format YAML secara default.
Konfigurasikan mybatis
Kami telah memperkenalkan perpustakaan mybatis-spring-boot-starte di pom.xml melalui inisialisasi spring, yang secara otomatis akan membantu kami menginisialisasi mybatis. Pertama, kami mengisi konfigurasi mybatis yang relevan di Application.yml:
# mybatis Mengkonfigurasi mybatis: # Konfigurasikan nama paket di mana kelas pemetaan terletak tipe-aliases-package: com.wooyoo.learning.dao.domain # Konfigurasikan jalur di mana file mapper xml berada, di sini adalah array mapper-locations:-mapper/productmapper.xml
Kemudian, tentukan kelas ProductMapper dalam kode:
@MapperPublic ProductMapper {Produk pilih (@param ("id") Long ID); void update (produk produk);}Di sini, selama kami menambahkan anotasi @Mapper, Spring Boot akan secara otomatis memuat kelas mapper saat menginisialisasi mybatis.
Alasan terbesar mengapa Spring Boot sangat populer adalah fitur konfigurasi otomatisnya. Pengembang hanya perlu memperhatikan konfigurasi komponen (seperti informasi koneksi basis data) tanpa peduli tentang cara menginisialisasi komponen individu, yang memungkinkan kami untuk fokus pada implementasi bisnis dan menyederhanakan proses pengembangan.
Mengakses database
Setelah menyelesaikan konfigurasi MyBatis, kami dapat mengakses database di antarmuka kami. Kami memperkenalkan kelas mapper melalui @Autowired di bawah ProductController dan memanggil metode yang sesuai untuk mengimplementasikan kueri dan memperbarui operasi pada produk. Di sini kami mengambil antarmuka kueri sebagai contoh:
@Restcontroller @requestMapping ("/Product") Public Class ProductController {@Autowired ProductMapper ProductMapper ProductMapper; @GetMapping ("/{id}") Produk publik getProductInfo (@pathvariable ("id") long productId) {return productMapper.select (productId); } // hindari terlalu lama dan hilangkan kode updateProductInfo}Kemudian masukkan beberapa informasi produk ke MySQL Anda dan Anda dapat menjalankan proyek untuk melihat apakah kueri berhasil.
Sejauh ini, kami telah berhasil mengintegrasikan mybatis ke dalam proyek kami, menambahkan kemampuan untuk berinteraksi dengan database. Tapi itu tidak cukup. Proyek web modern pasti akan mempercepat kueri basis data kami di cache. Selanjutnya, kami akan memperkenalkan cara mengintegrasikan Redis secara ilmiah ke dalam cache sekunder Mybatis untuk mewujudkan cache otomatis kueri basis data.
Redis terintegrasi
Konfigurasikan Redis
Sama seperti mengakses database, kita perlu mengonfigurasi informasi koneksi Redis. Tambahkan konfigurasi berikut ke file application.yml:
Spring: Redis: # Redis Database Index (default adalah 0). We use a database with index 3 to avoid conflicts with other databases database: 3 # redis server address (default is localhost) host: localhost # redis port (default is 6379) port: 6379 # redis access password (default is null) password: # redis connection timeout (unit is milliseconds) timeout: 0 # redis connection pool configuration pool: # Maximum number of available connections (default is 8, negative numbers represent infinite) max-active: 8 # Maximum number of idle connections (default is 8, negative numbers represent infinite) max-idle: 8 # Minimum number of idle connections (default is 0, this value is only effective) min-idle: 0 # Get the maximum connection waiting time from the connection pool (default is -1, unit is milliseconds, negative numbers indicate infinite) max-wait: -1
Semua yang tercantum di atas adalah konfigurasi yang umum digunakan, dan pembaca dapat memahami peran spesifik dari setiap item konfigurasi melalui informasi komentar. Karena kami telah memperkenalkan perpustakaan spring-boot-starter-data-redis di pom.xml, Spring Boot akan membantu kami secara otomatis memuat koneksi Redis dan kelas konfigurasi spesifik
org.springframework.boot.autoconfigure.data.redis.redisautoconfiguration. Melalui kelas konfigurasi ini, kita dapat menemukan bahwa lapisan yang mendasarinya menggunakan pustaka jedis secara default, dan menyediakan redistemplate dan stringtemplate di luar kotak.
Gunakan Redis sebagai cache Level 2
Prinsip caching sekunder Mybatis tidak akan dijelaskan dalam artikel ini. Pembaca hanya perlu tahu bahwa caching sekunder MyBatis dapat secara otomatis menangani kueri basis data, dan secara otomatis dapat memperbarui cache saat memperbarui data.
Menerapkan caching sekunder Mybatis sangat sederhana. Anda hanya perlu membuat kelas baru untuk mengimplementasikan antarmuka org.apache.ibatis.cache.cache.
Ada lima metode untuk antarmuka ini:
String getId (): Pengidentifikasi objek operasi cache Mybatis. Seorang mapper sesuai dengan objek operasi cache Mybatis.
void putobject (kunci objek, nilai objek): Masukkan hasil kueri ke dalam cache.
Objek getObject (kunci objek): Dapatkan hasil kueri yang di -cache dari cache.
Object RemestObject (Kunci Objek): Hapus tombol dan nilai yang sesuai dari cache. Hanya ditembakkan saat berguling. Secara umum, kami tidak perlu mengimplementasikannya. Untuk metode penggunaan tertentu, silakan merujuk ke: org.apache.iathis.cache.decorators.transactionalcache.
void clear (): Clear Cache Saat pembaruan terjadi.
int getSize (): Implementasi opsional. Mengembalikan jumlah cache.
ReadWritelock getReadWritelock (): Implementasi opsional. Digunakan untuk mengimplementasikan operasi cache atom.
Selanjutnya, kami membuat kelas Rediscache baru untuk mengimplementasikan antarmuka cache:
kelas publik Rediscache mengimplementasikan cache {private static final Logger logger = loggerFactory.getLogger (rediscache.class); Private Final ReadWritelock ReadWritelock = baru reentrantreadwritelock (); ID string final pribadi; // Cache Instance ID Private Redistemplate Redistemplate; private static final long expire_time_in_minutes = 30; // redis waktu kedaluwarsa rediscache publik (string id) {if (id == null) {lempar baru illegalArgumentException ("Cache instance memerlukan id"); } this.id = id; } @Override Public String getId () {return id; } / ** * Masukkan hasil kueri ke redis * * @param key * @param nilai * / @Override @suppresswarnings ("Uncecked") public void putoBject (kunci objek, nilai objek) {redistemplate redistemplate = getredistemplate (); Valueoperations opsforvalue = redistemplate.opsforvalue (); opsforvalue.set (kunci, nilai, kedaluwarsa_time_in_minutes, timeunit.minutes); logger.debug ("Letakkan hasil kueri ke redis"); } / ** * Dapatkan hasil kueri yang di -cache dari redis * * @param key * @return * / @Override Objek publik getObject (tombol objek) {redistemplate redistemplate = getRedistemplate (); Valueoperations opsforvalue = redistemplate.opsforvalue (); Logger.debug ("Dapatkan hasil kueri yang di -cache dari redis"); return opsforvalue.get (key); } / ** * Hapus hasil kueri yang di -cache dari redis * * @param Key * @return * / @Override @suppresswarnings ("Uncecked") Objek Publik RemestObject (Kunci Objek) {redistemplate redistemplate = getredistemplate (); redistemplate.delete (kunci); logger.debug ("Hapus hasil kueri yang di -cache dari redis"); kembali nol; } / ** * menghapus instance cache ini * / @Override public void clear () {redistemplate redistemplate = getredistemplate (); redistemplate.execute ((rediscallback) koneksi -> {connection.flushdb (); return null;}); logger.debug ("Hapus semua hasil kueri yang di -cache dari 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; }}Izinkan saya menjelaskan beberapa poin utama dalam kode di atas:
Cache tingkat kedua yang Anda terapkan harus memiliki konstruktor dengan ID, jika tidak kesalahan akan dilaporkan.
Kami menggunakan redistemplate yang dienkapsulasi pegas untuk mengoperasikan Redis. Semua artikel online yang memperkenalkan Redis ke cache sekunder level 2 menggunakan perpustakaan Jedis secara langsung, tetapi penulis percaya bahwa ini tidak cukup gaya musim semi. Selain itu, redistemplate merangkum implementasi yang mendasarinya. Jika kami tidak menggunakan jedis di masa depan, kami dapat secara langsung mengganti pustaka yang mendasarinya tanpa memodifikasi kode atas. Yang lebih nyaman adalah menggunakan redistemplate, kita tidak perlu peduli dengan rilis Redis Connections, jika tidak, akan mudah bagi para pemula untuk lupa untuk melepaskan koneksi dan menyebabkan aplikasi macet.
Perlu dicatat bahwa redistemplate tidak dapat direferensikan melalui autowire, karena rediscache bukan kacang di wadah musim semi. Jadi kita perlu secara manual memanggil metode Getbean dari wadah untuk mendapatkan kacang ini. Untuk metode implementasi tertentu, silakan merujuk ke kode di GitHub.
Metode serialisasi Redis yang kami gunakan adalah serialisasi JDK default. Oleh karena itu, objek kueri basis data (seperti kelas produk) perlu mengimplementasikan antarmuka yang dapat diserializable.
Dengan cara ini, kami menerapkan kelas cache yang elegan, ilmiah, dan Redis dengan gaya musim semi.
Nyalakan cache level 2
Selanjutnya, kita perlu mengaktifkan cache Level 2 di 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 namespace = "com.wooyoo.learning.dao.mapper.productmapper"> <!-Aktifkan cache sekunder berbasis redis-> <cache type = "com.wooyoo.learning.util.rediscache"/<pilih id = "pilih" resultType = "produk"> dari produk di mana id = #{{pilih "hancur" </produk "> dari Produk * dari ID = #{Pilih" Pilih "Produk"> dari Product "ID =" ID = #"ID =" ID = "Id = #{Pilih" Pilih "Produk ParameterType = "Produk" flushCache = "true"> Perbarui produk set nama = #{name}, price = #{price} di mana id = #{id} batas 1 </d untuk update> </mapper><cache type = "com.wooyoo.learning.util.rediscache"/> berarti mengaktifkan cache sekunder berbasis Redis, dan dalam pernyataan pembaruan, kami mengatur flushcache menjadi true, sehingga ketika memperbarui informasi produk, cache dapat secara otomatis tidak divalidasi (pada dasarnya, metode yang jelas dipanggil).
tes
Konfigurasikan database memori H2
Pada titik ini, kami telah menyelesaikan semua pengembangan kode, dan selanjutnya kami perlu menulis kode uji unit untuk menguji kualitas kode kami. Dalam proses pengembangan, kami menggunakan database MySQL, dan umumnya kami sering menggunakan database dalam memori selama pengujian. Di sini kami menggunakan H2 sebagai database yang digunakan dalam skenario pengujian kami.
Ini juga sangat mudah digunakan H2, Anda hanya perlu mengonfigurasinya saat menggunakan MySQL. Di file application.yml:
--- Musim Semi: Profil: Tes # Database Konfigurasi Data Sumber: URL: JDBC: H2: MEM: Uji Nama Pengguna: Kata Sandi Root: 123456 Driver-Class-Name: org.h2.Driver Skema: ClassPath: Schema.sql Data: classpath: data.sql
Untuk menghindari konflik dengan konfigurasi default, kami menggunakan --- untuk memulai paragraf baru dan menggunakan profil: tes untuk menunjukkan bahwa ini adalah konfigurasi di lingkungan pengujian. Kemudian cukup tambahkan anotasi @ActiveProfiles (profil = "test") ke kelas pengujian kami untuk mengaktifkan konfigurasi di lingkungan pengujian, sehingga Anda dapat beralih dari database MySQL ke database H2 dengan satu klik.
Dalam konfigurasi di atas, Schema.sql digunakan untuk menyimpan pernyataan pembuatan tabel kami, dan data.sql digunakan untuk menyimpan data memasukkan. Dengan cara ini, ketika kita menguji, H2 akan membaca dua file ini, menginisialisasi struktur tabel dan data yang kita butuhkan, dan kemudian menghancurkannya di akhir tes, yang tidak akan berdampak pada database MySQL kita. Ini adalah manfaat dari database dalam memori. Juga, jangan lupa untuk mengatur ruang lingkup ketergantungan H2 untuk menguji pom.xml.
Menggunakan Spring Boot sederhana, Anda dapat dengan mudah beralih basis data di lingkungan yang berbeda tanpa memodifikasi kode apa pun.
Menulis kode tes
Karena kami diinisialisasi melalui Spring Initializer, kami sudah memiliki kelas tes - SpringbootmybatiswithRedisApplicationTests.
Spring Boot menyediakan beberapa kelas alat yang memfasilitasi kami untuk melakukan pengujian antarmuka web, seperti TestRestTemplate. Kemudian di file konfigurasi, kami menyesuaikan level log untuk debug untuk memfasilitasi pengamatan log debug. Kode tes spesifik adalah sebagai berikut:
@Runwith (springrunner.class) @springboottest (webenvironment = springboottest.webenvironment.random_port) @ActiveProfiles (profil = "tes") kelas publik springbootmybatiswithredisapplicationTests {@localServerport private portor; @Autowired Private TestRestTemplate RestTemplate; @Test public void test () {long productId = 1; Produk Produk = restTemplate.getForObject ("http: // localhost:" + port + "/produk/" + productId, product.class); assertThat (product.getPrice ()). isEqualto (200); Produk newProduct = produk baru (); Long newPrice = new random (). nextLong (); newProduct.setName ("nama baru"); newProduct.setPrice (newPrice); restTemplate.put ("http: // localhost:" + port + "/produk/" + productId, newProduct); Produk TestProduct = restTemplate.getForObject ("http: // localhost:" + port + "/produk/" + productId, product.class); assertThat (testproduct.getprice ()). isequalto (newPrice); }}Dalam kode pengujian di atas:
Kami pertama -tama memanggil antarmuka GET dan menggunakan pernyataan Assert untuk menentukan apakah objek yang diharapkan telah diperoleh. Pada saat ini, objek produk akan disimpan di Redis.
Kemudian kami memanggil antarmuka put untuk memperbarui objek produk, dan cache Redis akan dibatalkan.
Akhirnya, kami memanggil antarmuka GET lagi untuk menentukan apakah kami telah memperoleh objek produk baru. Jika objek lama diperoleh, itu berarti bahwa kode cache tidak valid gagal dieksekusi dan ada kesalahan dalam kode, jika tidak itu berarti kode kami OK.
Menulis pengujian unit adalah kebiasaan pemrograman yang baik. Meskipun akan memakan waktu tertentu untuk Anda, ketika Anda perlu melakukan beberapa pekerjaan refactoring di masa depan, Anda akan berterima kasih kepada diri sendiri yang memiliki tes unit tertulis di masa lalu.
Lihat hasil tes
Kami mengklik untuk menjalankan kasus uji di IntelliJ, dan hasil tes adalah sebagai berikut:
Hijau ditampilkan, menunjukkan bahwa test case telah berhasil dieksekusi.
Meringkaskan
Artikel ini memperkenalkan cara dengan cepat membangun proyek web modern dengan Spring Boot, Mybatis dan Redis, dan juga memperkenalkan cara menulis tes unit dengan anggun di bawah boot Spring untuk memastikan kualitas kode kami. Tentu saja, ada masalah lain dengan proyek ini, yaitu cache Level 2 Mybatis hanya dapat di -cache dibatalkan dengan menyiram seluruh DB. Pada saat ini, beberapa cache yang tidak perlu dibatalkan juga dapat dibatalkan, sehingga memiliki keterbatasan tertentu.