使用SpringData同時(shí)訪問(wèn)MySQL和Neo4j數(shù)據(jù)庫(kù)
本文將以實(shí)例的方式探索「如何使用 Spring Data 同時(shí)訪問(wèn) MySQL 和 Neo4j 數(shù)據(jù)庫(kù)?」,涉及 Spring Boot 中多個(gè)數(shù)據(jù)源的配置、多個(gè)事務(wù)的配置,以及多組 Repository 的使用。
為了使演示工程更接近于實(shí)際,我們特為該工程設(shè)定一個(gè)場(chǎng)景:即使用該工程實(shí)現(xiàn) MySQL 到 Neo4j 的數(shù)據(jù)遷移。技術(shù)上會(huì)涉及使用兩組 Repository 進(jìn)行讀寫,以及關(guān)系型數(shù)據(jù)庫(kù)的表到 Neo4j 的 Node 和 Relationship 的轉(zhuǎn)換。
介紹工程結(jié)構(gòu)和主要代碼塊之前,先演示一下該工程實(shí)現(xiàn)的功能:
1 功能展示
該工程針對(duì) MySQL 的三張表進(jìn)行了數(shù)據(jù)遷移,遷移到 Neo4j 后變?yōu)榱?Node 和 Relationship。
MySQL 中的三張表為:actor(演員)、movie(電影)、actor_movie(演員電影關(guān)系表)。
建表語(yǔ)句與插入語(yǔ)句如下:
CREATE TABLE actor (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
nationality VARCHAR(100) NOT NULL,
year_of_birth INT NOT NULL
);
CREATE TABLE movie (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
released_at INT NOT NULL
);
CREATE TABLE actor_movie (
actor_id BIGINT NOT NULL,
movie_id BIGINT NOT NULL,
role VARCHAR(100) NOT NULL,
PRIMARY KEY (actor_id, movie_id)
);
INSERT INTO actor(name, nationality, year_of_birth) VALUES
('吳京', '中國(guó)', 1974),
('盧靖姍', '中國(guó)', 1985);
INSERT INTO movie(name, released_at) VALUES
('戰(zhàn)狼 Ⅱ', 2017),
('太極宗師', 1998),
('流浪地球 Ⅱ', 2023),
('我和我的家鄉(xiāng)', 2020);
INSERT INTO actor_movie(actor_id, movie_id, role) VALUES
(1, 1, '冷峰'),
(1, 2, '楊昱乾'),
(1, 3, '劉培強(qiáng)'),
(2, 1, 'Rachel'),
(2, 4, 'EMMA MEIER');
進(jìn)行數(shù)據(jù)遷移后,Neo4j 中的 Node 和 Relationship 如下:

功能展示完成后,下面介紹該示例工程的結(jié)構(gòu)以及關(guān)鍵代碼塊。
2 工程結(jié)構(gòu)及關(guān)鍵代碼分析
該示例工程是一個(gè)使用 Maven 管理的 Spring Boot 工程,其各依賴項(xiàng)及其版本如下:
Java: Liberica JDK 17.0.7 Maven: 3.9.2 Spring Boot: 3.4.5
2.1 工程結(jié)構(gòu)及依賴
該示例工程的結(jié)構(gòu)如下:
spring-data-jpa-and-neo4j-demo ├─ src │ ├─ main │ │ ├─ java │ │ │ └─ com.example.demo │ │ │ ├─ config │ │ │ │ ├─ MySQLConfig.java │ │ │ │ └─ Neo4jConfig.java │ │ │ ├─ repository │ │ │ │ ├─ graph │ │ │ │ │ ├─ GraphActorRepository.java │ │ │ │ │ └─ GraphMovieRepository.java │ │ │ │ └─ relational │ │ │ │ │ ├─ ActorRepository.java │ │ │ │ │ ├─ MovieRepository.java │ │ │ │ │ └─ ActorMovieRepository.java │ │ │ ├─ service │ │ │ │ ├─ MigrationService.java │ │ │ │ └─ impl │ │ │ │ └─ MigrationServiceImpl.java │ │ │ ├─ model │ │ │ │ ├─ graph │ │ │ │ │ ├─ GraphActor.java │ │ │ │ │ └─ GraphMovie.java │ │ │ │ └─ relational │ │ │ │ │ ├─ Actor.java │ │ │ │ │ ├─ Movie.java │ │ │ │ │ └─ ActorMovie.java │ │ │ └─ DemoApplication.java │ │ └─ resources │ │ └─ application.yaml │ └─ test │ └─ java │ └─ com.example.demo │ └─ service │ └─ MigrationServiceTest.java └─ pom.xml
可以看到,其是一個(gè)標(biāo)準(zhǔn)的 Maven 工程,DemoApplication.java 為啟動(dòng)類,application.yaml 為配置文件。config 包下用于放置配置類,MySQLConfig.java 和 Neo4jConfig.java 分別用于配置 MySQL 和 Neo4j 的連接信息讀取和事務(wù)管理。repository 包下用于放置訪問(wèn)數(shù)據(jù)庫(kù)的 Repository 接口,其中 relational 子目錄下放置的是訪問(wèn) MySQL 的 Repository,graph 子目錄下放置的是訪問(wèn) Neo4j 的 Repository。model 包下放置 Model 類,relational 子目錄下放置的是對(duì)應(yīng) MySQL 表的 Model 類,graph 子目錄下放置的是對(duì)應(yīng) Neo4j Node 的 Model 類。此外 service 包下用于放置服務(wù)類,我們編寫的 MigrationService.java 及其實(shí)現(xiàn)即是做 MySQL 到 Neo4j 數(shù)據(jù)遷移的。
該示例工程用到的依賴如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- driver -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>9.2.0</version>
</dependency>
<!-- test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
可以看到,該工程主要有兩項(xiàng)依賴 spring-boot-starter-data-jpa 和 spring-boot-starter-data-neo4j,前者用于訪問(wèn) MySQL,后者用于訪問(wèn) Neo4j。此外,使用 lombok 方便 Getters 和 Setters 的編寫,mysql-connector-j 為訪問(wèn) MySQL 的驅(qū)動(dòng),spring-boot-starter-test 為單元測(cè)試依賴。
2.2 工程配置
該工程的配置文件 application.yaml 的內(nèi)容如下
spring:
datasource:
jdbc-url: jdbc:mysql://localhost:3306/test?autoReconnect=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
username: root
password: root
jpa:
show-sql: true
neo4j:
uri: bolt://localhost:7687/neo4j
authentication:
username: neo4j
password: neo4j
logging:
level:
org.neo4j.ogm: DEBUG
org.springframework.data.neo4j: DEBUG
可以看到,我們配置了兩個(gè)數(shù)據(jù)源:spring.datasource 配置的是 MySQL 的連接信息,spring.neo4j 配置的是 Neo4j 的連接信息。此外,我們還開(kāi)啟了 SQL 和 Neo4j Cypher 語(yǔ)句的打印。
介紹完工程結(jié)構(gòu)、依賴和配置后,下面介紹關(guān)鍵的代碼塊。
2.3 Config 類
要支持在 Spring Boot 中同時(shí)操作 MySQL 和 Neo4j,配置類是關(guān)鍵。
下面是 MySQLConfig.java 的代碼:
package com.example.demo.config;
// ...
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackages = "com.example.demo.repository.relational",
entityManagerFactoryRef = "mysqlEntityManagerFactory",
transactionManagerRef = "mysqlTransactionManager"
)
public class MySQLConfig {
@Bean(name = "mysqlDataSource")
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource mysqlDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "mysqlEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean mysqlEntityManagerFactory(
EntityManagerFactoryBuilder builder,
@Qualifier("mysqlDataSource") DataSource dataSource) {
return builder.dataSource(dataSource)
.packages("com.example.demo.model.relational")
.persistenceUnit("mysql")
.build();
}
@Bean(name = "mysqlTransactionManager")
public PlatformTransactionManager mysqlTransactionManager(
@Qualifier("mysqlEntityManagerFactory") EntityManagerFactory factory) {
return new JpaTransactionManager(factory);
}
}
可以看到,我們?cè)谠擃愔兄付?MySQL Repository 的位置、數(shù)據(jù)庫(kù)連接信息在配置文件中的位置,并配置了 MySQL 的實(shí)體管理器和事務(wù)管理器。
下面是 Neo4jConfig.java 的代碼:
package com.example.demo.config;
// ...
@Configuration
@EnableTransactionManagement
@EnableNeo4jRepositories(
basePackages = "com.example.demo.repository.graph",
transactionManagerRef = "neo4jTransactionManager"
)
public class Neo4jConfig {
@Bean(name = "neo4jTransactionManager")
public PlatformTransactionManager transactionManager(Driver driver) {
return Neo4jTransactionManager.with(driver)
.build();
}
}
可以看到,我們?cè)谠撆渲妙愔兄付?Neo4j Repository 的位置并配置了 Neo4j 的事務(wù)管理器。
2.4 Model 類
Model 類用于對(duì)應(yīng) MySQL 數(shù)據(jù)庫(kù)的表或?qū)?yīng) Neo4j 數(shù)據(jù)庫(kù)的 Node。
下面是 Actor.java 的代碼,其對(duì)應(yīng) MySQL 的 actor 表。
package com.example.demo.model.relational;
// ...
@Data
@Entity(name = "actor")
public class Actor {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long actorId;
private String name;
private String nationality;
private Integer yearOfBirth;
}
下面是 GraphActor.java 的代碼,其對(duì)應(yīng) Neo4j 的 Actor Node。
package com.example.demo.model.graph;
// ...
@Data
@Node("Actor")
public class GraphActor {
@Id
@GeneratedValue
private Long id;
private Long actorId;
private String name;
private String nationality;
private Integer yearOfBirth;
}
relational 或 graph 包下其它的 Model 類與此兩者類似,為了控制篇幅,其代碼就不在這里一一列出了。
2.5 Repository 接口
Repository 用于真正與數(shù)據(jù)庫(kù)交互。
下面是 ActorRepository.java 的代碼,其用于對(duì) MySQL 的 actor 表進(jìn)行增刪改查。
package com.example.demo.repository.relational;
// ...
public interface ActorRepository extends JpaRepository<Actor, Long> {
}
下面是 GraphActorRepository.java 的代碼,其用于對(duì) Neo4j 的 Actor Node 進(jìn)行增刪改查。
package com.example.demo.repository.graph;
// ...
public interface GraphActorRepository extends Neo4jRepository<GraphActor, Long> {
@Transactional("neo4jTransactionManager")
@Query("""
UNWIND $actors AS actor
MERGE (a:Actor {actorId: actor.actorId})
ON CREATE SET a = actor
ON MATCH SET a += actor
""")
void batchInsertOrUpdate(List<Map<String, Object>> actors);
}
可以看到,與普通 JPA Repository 類似,Neo4j 的 Repository 上同樣支持編寫自定義查詢。之所以編寫該方法,是因?yàn)槭褂迷撟远x Cypher 方式編寫的 Actor 批量插入或更新方法比原生方式效率更高。
relational 或 graph 包下其它的 Repository 與此兩者類似,這里也不一一列出了。
2.6 Service 類
MigrationService 實(shí)現(xiàn)類的代碼如下,其對(duì)前面的 Model 和 Repository 進(jìn)行了使用,實(shí)現(xiàn)了 MySQL 到 Neo4j 的數(shù)據(jù)遷移。
package com.example.demo.service.impl;
// ...
@Service
public class MigrationServiceImpl implements MigrationService {
@Autowired
private ActorRepository actorRepository;
@Autowired
private MovieRepository movieRepository;
@Autowired
private ActorMovieRepository actorMovieRepository;
@Autowired
private GraphActorRepository graphActorRepository;
@Autowired
private GraphMovieRepository graphMovieRepository;
@Override
public void migrateActorsAndMovies() {
// migrate all actors
migrateAllActors();
// migrate all movies
migrateAllMovies();
// delete all ACTED_IN relations
graphMovieRepository.deleteAllActedInRelations();
// rebuild ACTED_IN relations
List<Map<String, Object>> actedInRelations = getAllActedInRelations();
graphMovieRepository.batchInsertOrUpdateActedInRelations(actedInRelations);
}
private void migrateAllActors() {
List<Actor> actors = actorRepository.findAll();
List<Map<String, Object>> graphActors = actors.stream()
.map(this::assembleActor)
.toList();
graphActorRepository.batchInsertOrUpdate(graphActors);
}
private void migrateAllMovies() {
List<Movie> movies = movieRepository.findAll();
List<Map<String, Object>> graphMovies = movies.stream()
.map(this::assembleMovie)
.toList();
graphMovieRepository.batchInsertOrUpdate(graphMovies);
}
private List<Map<String, Object>> getAllActedInRelations() {
List<ActorMovie> actorMovies = actorMovieRepository.findAll();
return actorMovies.stream()
.map(this::assembleActedIn)
.toList();
}
private Map<String, Object> assembleActor(Actor actor) {
GraphActor graphActor = new GraphActor();
BeanUtils.copyProperties(actor, graphActor);
graphActor.setId(null);
return ObjectToMapUtil.toMap(graphActor);
}
private Map<String, Object> assembleMovie(Movie movie) {
GraphMovie graphMovie = new GraphMovie();
BeanUtils.copyProperties(movie, graphMovie);
graphMovie.setId(null);
return ObjectToMapUtil.toMap(graphMovie);
}
private Map<String, Object> assembleActedIn(ActorMovie actorMovie) {
return Map.of(
"actorId", actorMovie.getId().getActorId(),
"movieId", actorMovie.getId().getMovieId(),
"role", actorMovie.getRole()
);
}
}
可以看到,該實(shí)現(xiàn)類對(duì) MySQL 進(jìn)行讀取,對(duì) Neo4j 進(jìn)行寫入,實(shí)現(xiàn)了兩個(gè)數(shù)據(jù)庫(kù)的模式轉(zhuǎn)換和數(shù)據(jù)遷移。
需要注意的是,我們使用了一個(gè) Java 對(duì)象到 Map 類型轉(zhuǎn)換的工具類 ObjectToMapUtil.java,這是因?yàn)?Neo4j 的 Repository 目前還不能很好的支持直接傳入一個(gè) List<Actor> 對(duì)象。
最后,在單元測(cè)試類中調(diào)用該實(shí)現(xiàn)類后,即可出現(xiàn)文章開(kāi)頭展示的效果。
3 小結(jié)
綜上,我們以實(shí)現(xiàn) MySQL 到 Neo4j 數(shù)據(jù)遷移為目的演示了如何使用 Spring Data 同時(shí)訪問(wèn) MySQL 和 Neo4j 數(shù)據(jù)庫(kù)。
參考資料
[1] Spring: Spring Data JPA - https://docs.spring.io/spring-data/jpa/reference/jpa.html
[2] Spring: Spring Data Neo4j Reference Documentation - https://docs.spring.io/spring-data/neo4j/reference/
到此這篇關(guān)于使用SpringData同時(shí)訪問(wèn)MySQL和Neo4j數(shù)據(jù)庫(kù)的文章就介紹到這了,更多相關(guān)SpringData同時(shí)訪問(wèn)MySQL和Neo4j內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot整合dubbo設(shè)置全局唯一ID進(jìn)行日志追蹤的示例代碼
這篇文章主要介紹了springboot整合dubbo設(shè)置全局唯一ID進(jìn)行日志追蹤,本文通過(guò)圖文示例相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10
Java 判斷兩個(gè)字符串是否由相同的字符組成的實(shí)例
今天小編就為大家分享一篇Java 判斷兩個(gè)字符串是否由相同的字符組成的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-07-07
一篇文章教你用Java使用JVM工具檢測(cè)問(wèn)題
這篇文章主要介紹了深入理解Java使用JVM工具檢測(cè)問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-09-09
Java手動(dòng)實(shí)現(xiàn)Redis的LRU緩存機(jī)制
在Java中LRU的實(shí)現(xiàn)方式是使用HashMap結(jié)合雙向鏈表,HashMap的值是雙向鏈表的節(jié)點(diǎn),雙向鏈表的節(jié)點(diǎn)也保存一份key value。2021-05-05
Java ConcurrentModificationException異常解決案例詳解
這篇文章主要介紹了Java ConcurrentModificationException異常解決案例詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-09-09
SpringBoot實(shí)現(xiàn)自定義事件的方法詳解
這篇文章將用實(shí)例來(lái)和大家介紹一下如何在SpringBoot中自定義事件來(lái)使用觀察者模式。文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)SpringBoot有一定的幫助,需要的可以參考一下2022-06-06
關(guān)于報(bào)錯(cuò)IDEA Terminated with exit code
如果在IDEA構(gòu)建項(xiàng)目時(shí)遇到下面這樣的報(bào)錯(cuò)IDEA Terminated with exit code 1,那必然是Maven的設(shè)置參數(shù)重置了,導(dǎo)致下載錯(cuò)誤引起的,本文給大家分享兩種解決方法,需要的朋友可以參考下2022-08-08
部署springboot項(xiàng)目到云服務(wù)器的兩種方式(jar+war)
本文主要介紹了部署springboot項(xiàng)目到云服務(wù)器的兩種方式,主要介紹了jar和war兩種方式,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12
史上最簡(jiǎn)單的MyBatis動(dòng)態(tài)SQL入門示例代碼
動(dòng)態(tài)sql,可以根據(jù)用戶對(duì)字段選擇和輸入,動(dòng)態(tài)生成一條sql執(zhí)行。接下來(lái)通過(guò)本文給大家分享MyBatis動(dòng)態(tài)SQL入門示例代碼,一起看看吧2017-03-03

