使用Hazelcast作為Spring資料儲存庫的開源案例

banq發表於2020-09-17

在我們的付款系統中,使用了非常簡單的快取方式。我們有本地的EhCache,它工作得很好,是在JDBC層提供的。這種設計的缺點是:
  • 這是本地快取。沒有資料更改傳播到其他節點。
  • 不涉及JPA。

解決上述問題的好方法是將Hazelcast包裝到Spring Data API中, 稱為spring-data-hazelcast
在該儲存庫中,我將向您展示如何使用H2資料庫支援的Hazelcast Spring Data API 構建完整的只讀快取。
首先,一定要看一下spring-data-jpa-hazelcast-migration 如果您是Spring Data JPA新手,那麼本指南將非常有幫助。然後確保您熟悉Hazelcast MapLoader
 

使用HazelcastRead-through快取

  • maven依賴

<properties>
    <java.version>1.8</java.version>
    <spring-data-hazelcast-version>2.2.5</spring-data-hazelcast-version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>com.hazelcast</groupId>
        <artifactId>spring-data-hazelcast</artifactId>
        <version>${spring-data-hazelcast-version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

spring-data-hazelcast外掛已經打包好了hazelcast,因此無需擔心版本問題。大讚!
  • 第二步

快取實體是儲存在H2資料庫,並透過MapLoader提供給hazelcast的,該實體是此read-through體系結構的引擎:

@KeySpace("persons")
@Entity
@Table(name = "persons")
public class Person implements Serializable {

@Id
@javax.persistence.Id
private Long personId;
private String name;
private String surname;
private String role;
private Long teamId;

@Column(name = "PERSON_ID")
public Long getPersonId() {
    return personId;
}

public void setPersonId(Long personId) {
    this.personId = personId;
}

@Column(name = "NAME")
public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

@Column(name = "SURNAME")
public String getSurname() {
    return surname;
}

public void setSurname(String surname) {
    this.surname = surname;
}

@Column(name = "ROLE")
public String getRole() {
    return role;
}

public void setRole(String role) {
    this.role = role;
}

@Column(name = "TEAM_ID")
public Long getTeamId() {
    return teamId;
}

public void setTeamId(Long teamId) {
    this.teamId = teamId;
}


此處的KeySpace註釋表示,hazelcast 將使用名為“persons”的IMap將資料儲存在記憶體中。
  • 第三步

MapLoader實現將資料庫和Hazelcast粘合在一起:

@Component
public class HazelcastMapStore implements ApplicationContextAware, MapLoader<Long, Person> {

private static PersonsJPARepository personsJPARepository;

@Override
public Person load(Long personId) {
    System.out.println("Loading by key: "+personId);
    return personsJPARepository.findById(personId).get();
}

@Override
public Map<Long, Person> loadAll(Collection<Long> collection) {
    System.out.println("Loading collections of IDS: ");
    Map<Long, Person> result = new HashMap<>();
    for (Long key : collection) {
        Person productMap = this.load(key);
        if (productMap != null) {
            result.put(key, productMap);
        }
    }
    return result;
}

@Override
public Iterable<Long> loadAllKeys() {
    System.out.println("Getting all the keys!");
    return personsJPARepository.findAllId();
}

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    personsJPARepository = applicationContext.getBean(PersonsJPARepository.class);
}

MapLoader已準備就緒,現在每次當請求的實體不在記憶體中時,Hazelcast都會命中MapLoader.load(personId)其他兩種方法只是預熱快取方法,用於在第一次快取命中之前載入快取的資料。
  • 第四步

建立"persons" IMap,並連線到已經建立的HazelcastMapStore。

@Configuration
public class HazelcastConfiguration {

@Bean
public Config hazelcastConfig(@Lazy HazelcastMapStore mapStore) {
    return new Config().setInstanceName("hazelcast-instance").addMapConfig(
            new MapConfig().setName("persons")
                    .setMapStoreConfig(
                            new MapStoreConfig().setEnabled(true).setInitialLoadMode(MapStoreConfig.InitialLoadMode.EAGER)
                                    .setImplementation(mapStore)
                    ));
}

@Bean
public HazelcastInstance hazelcastInstance(Config config) {
    return Hazelcast.newHazelcastInstance(config);
}


如果在將您的儲存庫連線到MapLoader時遇到問題,請檢視此 Stackoverflow討論
  • 第五步

配置用於Hazelcast(快取)和JPA(訪問H2 DB)的Spring資料儲存庫

@SpringBootApplication(exclude = {
    HazelcastAutoConfiguration.class
})
@EnableHazelcastRepositories(basePackages={"com.example.hazelcast.demo.repositories.hz"})
@EnableJpaRepositories(basePackages={"com.example.hazelcast.demo.repositories.jpa"})
public class DemoApplication {

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

EnableHazelcastRepositories,EnableJpaRepositories只是說明Spring資料儲存庫的位置。
這是完整的程式碼:

package com.example.hazelcast.demo.repositories.hz;

import com.example.hazelcast.demo.model.Person;
import org.springframework.data.hazelcast.repository.HazelcastRepository;

public interface PersonsHazelcastRepository extends HazelcastRepository<Person, Long> {
    Person findPersonByPersonId(Long personId);
}


package com.example.hazelcast.demo.repositories.jpa;

import com.example.hazelcast.demo.model.Person;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;

public interface PersonsJPARepository extends CrudRepository<Person, Long> {
    @Query("SELECT n.id FROM Person n")
    Iterable<Long> findAllId();
}


 

相關文章