Cách Sử dụng Spring Data để Truy cập Đồng thời vào Cơ sở Dữ liệu MySQL và Neo4j - 888bets

| Mar 20, 2025 min read

05 tháng 5 năm 2025 - Máy tính

Bài viết này sẽ trình bày chi tiết về cách sử dụng Spring Data để truy cập đồng thời vào hai cơ sở dữ liệu MySQL và Neo4j. Nội dung bao gồm cấu hình nhiều nguồn dữ liệu trong Spring Boot, quản lý giao dịch cho cả hai hệ thống, cũng như cách sử dụng các nhóm Repository khác nhau.

Để làm cho dự án minh họa gần với thực tế hơn, chúng ta sẽ đặt ra một kịch bản cụ thể: việc di chuyển dữ liệu từ MySQL sang Neo4j. Về mặt kỹ thuật, chúng ta sẽ sử dụng hai nhóm Repository để đọc và ghi dữ liệu, cũng như chuyển đổi bảng từ cơ sở dữ liệu quan hệ thành nút (node) và mối liên kết (relationship) trong Neo4j.

Trước khi đi sâu vào cấu trúc của dự án và các đoạn mã chính, hãy xem qua chức năng mà dự án này có thể thực hiện:

1. Hiển thị Chức năng

Dự án này thực hiện việc di chuyển dữ liệu từ ba bảng trong MySQL sang Neo4j, nơi chúng trở thành các node và relationship.

Ba bảng trong MySQL là:

  • actor (diễn viên)
  • movie (phim)
  • actor_movie (mối liên hệ giữa diễn viên và phim)

Câu lệnh tạo bảng và chèn dữ liệu như sau:

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 ('Ngô Kinh', 'Trung Quốc', 1974),
       ('Lư Tĩnh Sương', 'Trung Quốc', 1985);

INSERT INTO movie (name, released_at)
VALUES ('Chiến Lang II', 2017),
       ('Thái Cực Tông Sư', 1998),
       ('Hành Tinh Drift II', 2023),
       ('Tôi Và Quê Hương Tôi', 2020);

INSERT INTO actor_movie (actor_id, movie_id, role)
VALUES (1, 1, 'Lãnh Phong'),
       (1, 2, 'Dương Dật Khôn'),
       (1, 3, 'Lưu Bồi Cường'),
       (2, 1, 'Rachel'),
       (2, 4, 'EMMA MEIER');

Sau khi di chuyển dữ liệu, các node và relationship trong Neo4j sẽ trông như sau: ![](Neo4j Graph)

Tiếp theo, chúng ta sẽ phân tích cấu trúc và các đoạn mã quan trọng của dự án này.

g88.vin 2. Cấu trúc Dự án và Phân tích Mã Nguồn

Dự án mẫu này được quản lý bởi Maven và sử dụng Spring Boot. Các phiên bản phụ thuộc chính như sau:

Java: Liberica JDK 17.0.7
Maven: 3.9.2
Spring Boot: 3.4.5

2.1. Cấu trúc Dự án và Phụ thuộc

Cấu trúc của dự án như sau:

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

Như có thể thấy, đây là một dự án chuẩn Maven, với DemoApplication.java đóng vai trò là lớp khởi chạy và application.yaml là tệp cấu hình. Gói config chứa các lớp cấu hình, chẳng hạn như MySQLConfig.javaNeo4jConfig.java, được dùng để thiết lập thông tin kết nối và quản lý giao dịch cho MySQL và Neo4j.

Gói repository chứa các giao diện Repository để tương tác với cơ sở dữ liệu. Trong đó, gói con relational chứa các Repository dành cho MySQL, còn gói con graph chứa các Repository dành cho Neo4j.

Gói model chứa các lớp mô hình (Model). Gói con relational chứa các Model tương ứng với bảng MySQL, trong khi gói con graph chứa các Model tương ứng với các node trong Neo4j.

Ngoài ra, gói service chứa các lớp dịch vụ, bao gồm MigrationService.java và triển khai của nó, thực hiện nhiệm vụ di chuyển dữ liệu từ MySQL sang Neo4j.

Dưới đây là danh sách các phụ thuộc cần thiết trong pom.xml:

<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>

Chúng ta có thể thấy rằng dự án này chủ yếu dựa vào hai phụ thuộc chính: spring-boot-starter-data-jpa (dùng để truy cập MySQL) và spring-boot-starter-data-neo4j (dùng để truy cập Neo4j). Ngoài ra, lombok giúp đơn giản hóa việc viết Getters và Setters, mysql-connector-j là trình điều khiển cho MySQL, và spring-boot-starter-test hỗ trợ kiểm thử đơn vị.

2.2. Cấu hình Dự án

Tệp cấu hình application.yaml có nội dung như sau:

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
    authentication:
      username: neo4j
      password: neo4j
logging:
  level:
    org.neo4j.ogm: DEBUG
    org.springframework.data.neo4j: DEBUG

Như vậy, chúng ta đã cấu hình hai nguồn dữ liệu: spring.datasource dành cho thông tin kết nối MySQL, và spring.neo4j dành cho thông tin kết nối Neo4j. Bên cạnh đó, chúng ta cũng bật chế độ in câu lệnh SQL và Cypher của Neo4j.

Sau khi tìm hiểu cấu trúc, phụ thuộc và cấu hình, bây giờ chúng ta sẽ phân tích các đoạn mã quan trọng.

2.3. Lớp Config

Để hỗ trợ thao tác đồng thời trên MySQL và Neo4j trong Spring Boot, lớp cấu hình đóng vai trò then chốt.

Dưới đây là mã nguồn của 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() [888bets](https://www.kongken.com)  {
        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);
    }
}

Như có thể thấy, trong lớp này, chúng ta chỉ định vị trí của các Repository cho MySQL, thông tin kết nối trong tệp cấu hình, và cấu hình quản lý thực thể và giao dịch cho MySQL.

Tiếp theo là mã nguồn của Neo4jConfig.java:

package [Sam86 Club Choi Game Bài](/post/4011/)  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();
    }
}

Trong lớp cấu hình này, chúng ta chỉ định vị trí của các Repository cho Neo4j và cấu hình quản lý giao dịch cho Neo4j.

2.4. Lớp Model

Lớp Model được sử dụng để ánh xạ bảng trong MySQL hoặc node trong Neo4j.

Dưới đây là mã nguồn của Actor.java, tương ứng với bảng actor trong MySQL:

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;
}

Dưới đây là mã nguồn của GraphActor.java, tương ứng với node Actor trong Neo4j:

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;
}

Các Model khác trong gói relational hoặc graph tương tự như hai ví dụ trên, vì vậy chúng tôi sẽ không liệt kê hết ở đây.

2.5. Giao diện Repository

Repository được sử dụng để tương tác trực tiếp với cơ sở dữ liệu.

Dưới đây là mã nguồn của ActorRepository.java, dùng để thực hiện các thao tác CRUD trên bảng actor trong MySQL:

package com.example.demo.repository.relational;

// ...

public interface ActorRepository extends JpaRepository<Actor, Long> {}

Dưới đây là mã nguồn của GraphActorRepository.java, dùng để thực hiện các thao tác CRUD trên node Actor trong Neo4j:

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);
}

Như có thể thấy, tương tự như Repository JPA thông thường, Repository Neo4j cũng hỗ trợ việc viết các truy vấn tùy chỉnh. Lý do chúng ta viết phương thức này là vì cách tiếp cận Cypher tùy chỉnh này hiệu quả hơn so với cách tiếp cận mặc định.

Các Repository khác trong gói relational hoặc graph tương tự như hai ví dụ trên.

2.6. Lớp Service

Mã nguồn của lớp triển khai MigrationService như sau, sử dụng các Model và Repository đã đề cập trước đó để thực hiện việc di chuyển dữ liệu từ MySQL sang Neo4j:

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() {
        // Di chuyển tất cả các diễn viên
        migrateAllActors();

        // Di chuyển tất cả các phim
        migrateAllMovies();

        // Xóa tất cả mối liên hệ ACTED_IN
        graphMovieRepository.deleteAllActedInRelations();

        // Tái tạo mối liên hệ ACTED_IN
        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()
        );
    }
}

Như vậy, lớp triển khai này đọc dữ liệu từ MySQL và ghi dữ liệu vào Neo4j, hoàn thành quá trình chuyển đổi mô hình và di chuyển dữ liệu giữa hai cơ sở dữ liệu.

Lưu ý rằng chúng ta sử dụng một công cụ chuyển đổi đối tượng Java thành kiểu Map (ObjectToMapUtil.java), vì Repository của Neo4j hiện tại chưa hỗ trợ tốt việc truyền trực tiếp một danh sách đối tượng List<Actor>.

Cuối cùng, sau khi gọi lớp triển khai này trong lớp kiểm thử đơn vị, chúng ta sẽ nhận được kết quả như đã trình bày ở phần đầu bài viết.

3. Kết luận

Như đã thảo luận, chúng ta đã minh họa cách sử dụng Spring Data để truy cập đồng thời vào MySQL và Neo4j, với mục tiêu di chuyển dữ liệu từ MySQL sang Neo4j.

Mã nguồn đầy đủ của dự án mẫu đã được gửi lên GitHub, mời bạn quan tâm hoặc Fork.

[1] Spring: Spring Data JPA - [2] Spring: Tài liệu Tham khảo Spring Data Neo4j -

#Spring #Java #MySQL #Neo4j