Phân Tích Chu Trình Thực Thi SqlSession Trong MyBatis

Khởi Tạo SqlSession và SqlSessionFactory

SqlSession đại diện cho một phiên làm việc độc lập với cơ sở dữ liệu. Do tính chất phi tồn tại vĩnh viễn của phiên này,生命周期 của SqlSession cũng mang tính ngắn hạn. Mỗi lần tương tác với database yêu cầu khởi tạo một phiên mới. Nếu cần thực hiện nhiều câu lệnh SQL, phiên đó vẫn giữ nguyên trạng thái cho đến khi được đóng lại.

Hành trình bắt đầu từ việc xây dựng SqlSessionFactory thông qua builder. Quy trình này bao gồm việc đọc file cấu hình XML để định hình các thông tin môi trường, sau đó đóng gói thành đối tượng DefaultSqlSessionFactory. Dưới đây là luồng logic tạo ra factory:

public SqlSessionFactory build(Configuration sourceConfig) {
    // Khởi tạo đối tượng quản lý cấu hình chính
    return new DefaultSqlSessionFactory(sourceConfig);
}

public SqlSessionFactory build(InputStream stream) {
    // Chuyển đổi dòng nhập liệu thành parser XML
    XMLConfigBuilder parser = new XMLConfigBuilder(stream);
    // Giải mã và trả về configuration
    return build(parser.parse());
}

Từ SqlSessionFactory, ứng dụng gọi phương thức mở phiên làm việc để lấy SqlSession. Phương thức này chịu trách nhiệm thiết lập kết nối, giao dịch và chọn loại Executor phù hợp trước khi trả về phiên.

private SqlSession openSessionFromDataSource(ExecutorType type, boolean autoCommitFlag) {
    Environment env = configuration.getEnvironment();
    
    // Lấy thông tin nguồn dữ liệu và cài đặt giao dịch
    DataSource dataSource = getDataSource(env);
    Transaction tx = transactionFactory.newTransaction(dataSource, autoCommitFlag);
    
    // Xây dựng Executor dựa trên loại cấu hình
    Executor executor = configuration.newExecutor(tx, type);
    
    // Trả về phiên mặc định với tham số liên quan
    return new DefaultSqlSession(configuration, executor, autoCommitFlag);
}

Trong quá trình dùng thử, ứng dụng thường viết như sau để lấy phiên và bắt đầu thao tác:

String configFileLocation = "database-config.xml";
// Đọc và xây dựng bộ tạo phiên
SqlSessionFactory sqlFactory = new SqlSessionFactoryBuilder()
    .build(Resources.getResourceAsReader(configFileLocation));

// Mở một phiên làm việc mới
SqlSession dbSession = sqlFactory.openSession();

Quá trình tạo phiên tập trung vào các bước chính:

  • Trích xuất Environment từ Configuration;
  • Lấy DataSource từ Environment;
  • Cấp phát Connection từ DataSource;
  • Tạo Transaction trên kết nối;
  • Khởi tạo Executor (thành phần thực thi thật sự);
  • Gói gọn thành đối tượng SqlSession.

Cơ Chế Dynamic Proxy Cho Mapper

MyBatis sử dụng cơ chế proxy để ánh xạ giữa interface và SqlSession. Khi gọi phương thức của DAO interface, hệ thống tự động chuyển hướng yêu cầu tới MapperProxy.

Việc tìm kiếm Mapper bắt đầu từ SqlSession, chuyển tiếp sang MapperRegistry, và cuối cùng là MapperProxyFactory. Factory này sử dụng Java Reflection để sinh ra một đối tượng proxy thực tế.

public T newInstance(SqlSession ctx) {
    final MapperProxy proxyObj = new MapperProxy(ctx, mapperInterface);
    // Sử dụng Proxy mới của JDK
    return (T) Proxy.newProxyInstance(
        mapperInterface.getClassLoader(), 
        new Class[] { mapperInterface }, 
        proxyObj
    );
}

Sử dụng interface trở nên thuận tiện hơn cho lập trình viên:

UserDAO userRepo = sqlSession.getMapper(UserDAO.class);
User account = new User();
account.setUserName("Admin");

Vai Trò Của Executor

SqlSession chỉ đóng vai trò bề mặt, mọi hoạt động truy vấn hay cập nhật đều do Executor đảm nhận. Loại Executor được chọn phụ thuộc vào cấu hình `ExecutorType`: SIMPLE (mặc định), REUSE, hoặc BATCH.

public Executor newExecutor(Transaction tx, ExecutorType execType) {
    Executor resultExec = null;
    
    if (ExecutorType.BATCH == execType) {
        resultExec = new BatchExecutor(this, tx);
    } else if (ExecutorType.REUSE == execType) {
        resultExec = new ReuseExecutor(this, tx);
    } else {
        resultExec = new SimpleExecutor(this, tx);
    }
    
    // Nếu bật cache, bọc quanh Executor bằng CachingExecutor
    if (cacheEnabled) {
        resultExec = new CachingExecutor(resultExec);
    }
    
    // Áp dụng các plugin chặn lại
    return interceptorChain.pluginAll(resultExec);
}

Các loại Executor cơ bản:

  • SimpleExecutor: Tạo Statement mới cho mỗi lần thực thi.
  • ReuseExecutor: Tái sử dụng Statement đã được chuẩn bị sẵn.
  • BatchExecutor: Tích hợp câu lệnh SQL và thực thi theo lô.

Dòng Chạy Thực Thi SQL

Khi MapperProxy nhận được lời gọi phương thức, nó điều phối lại MapperMethod. Sau đó, MapperMethod quyết định gọi hàm tương ứng trên SqlSession (select, insert, update, delete).

public Object execute(SqlSession ctx, Object[] params) {
    // Phân loại lệnh CRUD
    if (SqlCommandType.SELECT == command.getType()) {
        if (method.returnsMany()) {
            return executeForMany(ctx, params);
        } else {
            return ctx.selectOne(command.getName(), params);
        }
    }
    // Xử lý INSERT, UPDATE, DELETE tương tự
    return rowCountResult(ctx.delete(command.getName()));
}

Tiếp tục đi sâu xuống Executor.query():

public <E> List<E> query(String statement, Object parameter, RowBounds bounds) {
    MappedStatement ms = configuration.getMappedStatement(statement);
    // Chuyển giao công việc cho Executor thực thi
    return executor.query(ms, parameter, bounds);
}

Đối với loại BATCH, nếu muốn tăng hiệu suất ghi hàng loạt, ta có thể cấu hình thủ công.

@Autowired
private SqlSessionFactory factory;

public void runBatchInsert() {
    // Khởi tạo SESSION mode BATCH
    SqlSession session = factory.openSession(ExecutorType.BATCH, false);
    FooMapper mapper = session.getMapper(FooMapper.class);
    
    for (int i = 0; i < 5000; i++) {
        DataEntity item = new DataEntity();
        item.setDataValue(i);
        mapper.insert(item);
        
        // Commit thủ công định kỳ để tránh tràn buffer
        if (i % 1000 == 0) {
            session.commit();
            session.clearCache();
        }
    }
    session.close();
}

Ngoại Ngữ Và Chuẩn Bị Câu Lệnh

Chi tiết xử lý câu lệnh nằm trong StatementHandler. Nó chịu trách nhiệm chuẩn bị PreparedStatement, gắn tham số và thực thi.

public StatementHandler newStatementHandler(Executor e, MappedStatement ms) {
    RoutingStatementHandler handler = new RoutingStatementHandler(e, ms);
    // Plugin hóa để hỗ trợ pagination
    return interceptorChain.pluginAll(handler);
}

Tham số hóa câu hỏi thực hiện bởi ParameterHandler, đặc biệt chú trọng mapping kiểu dữ liệu thông qua TypeHandler.

public void setParameters(PreparedStatement ps) throws SQLException {
    // Duyệt danh sách mapping
    for (ParameterMapping pm : parameterMappings) {
        TypeHandler th = pm.getTypeHandler();
        // Gán giá trị thực tế vào PreparedStatement
        th.setParameter(ps, pm.getIndex(), paramValue, pm.getJdbcType());
    }
}

Sau khi thực thi xong, kết quả trả về được xử lý bởi ResultSetHandler, tự động gán giá trị từ ResultSet vào object thông qua property map và TypeHandler.

Thẻ: mybatis JavaCore orm DatabaseIntegration jdbc

Đăng vào ngày 3 tháng 10 lúc 01:09