Mybatis va Spring

MyBatis là một persistence framework mã nguồn mở, đơn giản, gọn nhẹ và dễ sử dụng. Trước đây MyBatis được gọi là iBatis và được viết ra năm 2002 bởi Clinton Begin. Mybatis 3 được thiết kế lại từ iBatis, có hỗ trợ Mapper và Annotations. Sự khác nhau lớn nhất giữa Mybatis và các persistence framework khác đó là Mybatis nhấn mạnh việc sử dụng SQL, trong khi các framework khác như Hibernate sử dụng một ngôn ngữ truy vấn tùy chỉnh. Sau đây sẽ là một ví dụ về việc sử dụng Mybatis trong một project Spring, với mybatis-spring

Data Source

Bất kì Data Source nào đều được chấp nhận, ví dụ như org.springframework.jdbc.jar:

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="location" value="classpath:jdbc.properties"/>
</bean>

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

Cài đặt SqlSessionFactory

Với XML:

# SqlSessionFactoryBuilder

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" /> <!-- Obliged -->
    <!-- Indicate location of mapper files where exists mysql statements -->
    <property name="mapperLocations" value="classpath*:/mybatis/*Mapper.xml"/>
    <!-- Indicate mybatis config files where exists typeAliases, settings, etc -->
    <property name="configLocation" value="classpath:/mybatis/mybatis-config.xml"/>
</bean>

Với Anotation:

# SqlSessionFactoryBean

String resource = "path/to/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

Định nghĩa mappers

Có 2 cách để khai báo mappers:

  • Annotation based
  • XML based

Khi định nghĩa dựa trên XML:

  • Báo với SqlSessionFactoryBean nơi để tìm được nó bởi mapperLocations
  • Mapper namespace cần phải đặt đường dẫn đầy đủ đối với mapper interface.
  • id phải cùng tên với phương thức trong mapper namespace, tương tự cả với tham số, kiểu trả về, ...

Ví dụ dưới đây bao gồm cả XML và anotation

<mapper namespace="com.dong.web.mapper.UserMapper">

    <select id="getRowCount" resultType="int">
        select count(*) from tb_user
    </select>

</mapper> 
public interface UserMapper {

    int getRowCount();

    @Select("select * from tb_user")
    List<User> getAllUsers();

}

SqlSession

Với Mybatis cơ bản:

SqlSession session = sqlSessionFactory.openSession();

try {
    ...
    session.commit();
} catch(Exception e) {
    e.printStackTrace();
    session.rollback(); 
} finally {
    session.close();
}

Nhưng với mybatis-spring, bean sẽ được inject với một safe thread SqlSession, cho phép tự động commit, rollbacks và close session dựa trên Spring’s transaction configuration. Chúng ta có 2 cách để lấy session trong DAOs:

  • SqlSessionTemplate
  • SqlSessionDaoSupport Nhưng tôi thích sử dụng MapperScannerConfigurer hoặc MapperFactoryBean trực tiếp để tránh mã hoá DAO bằng tay. MapperScannerConfigurer thậm chí có thể tự động quét mapper interfaces. Đó là sự lựa chọn rất tốt!

Transaction

Đầu tiên, trong mybatis-spring, bạn cần bật Spring transaction processing:

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>

Sau đó thêm @Transactional annotation vào tầng services.

SqlSessionTemplate

SqlSessionTemplate implements SqlSession, nó là một thread an toàn và có thể được chia sẻ bởi nhiều DAOs hoặc mappers. Nó được sử dụng để:

  • Chắc chắn rằng SqlSession đang được sử dụng là một liên kết với Spring transaction hiện tại khi gọi truy vấn SQL.
  • Quản lý session life-cycle, bao gồm closing, committing hoặc rolling back session nếu cần
  • Dịch MyBatis exceptions thành Spring DataAccessExceptions.

Vì vậy, ý tưởng ở đây là tạo bean cho SqlSessionTemplate và inject nó vào tầng DAO.

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg index="0" ref="sqlSessionFactory" />
</bean>

<!-- Inject it in DAO layer -->
<bean id="userDAO" class="com.dong.web.dao.UserDaoImpl">
    <property name="sqlSession" ref="sqlSession" />
</bean>

DAO class với injected sqlSession:

public class UserDaoImpl implements UserDao {

    private SqlSession sqlSession;

    public void setSqlSession(SqlSession sqlSession) {
        this.sqlSession = sqlSession;
    }

    public List<User> getAllUsers() {
        return sqlSession.select("com.dong.web.mapper.UserMapper.getAllUsers");
    }
}

Batch Processing

Để bật tính năng batch:

<bean id="sqlSession" class="com.dong.web.dao.UserServiceImpl">
    <constructor-arg index="0" ref="sqlSessionFactory" />
    <constructor-arg index="1" value="BATCH" />
</bean>

Bây giờ tất cả các câu lệnh SQL đều sẽ được batched:

public void insertUsers(User[] users) {
    for (User user : users) {
        sqlSession.insert("org.dong.web.mapper.UserMapper.insertUser", user);
    }
}

SqlSessionDaoSupport

Đây là một lớp trừu tượng hỗ trợ SqlSession. Chúng ta có thể gọi getSqlSession() bằng cách kế thừa SqlSessionDaoSupport để lấy SqlSession:

public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao {
    public User getUser(String userId) {
        return (User) getSqlSession().selectOne("com.dong.web.mapper.UserMapper.getUser", userId);
    }    
}

MapperFactoryBean

Nó được sử dụng để tránh mã hóa các DAO bằng tay bởi SqlSessionDaoSupport hoặc SqlSessionTemplate, do đó không có DAO ở đây trong mã java! Nó xử lý tạo một SqlSession cũng như đóng nó. Nếu có Spring transaction đang diễn ra, session cũng sẽ được committed hoặc rolled back khi transaction hoàn tất.

<!-- Create a MapperFactoryBean for UserMapper interface -->
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
    <property name="mapperInterface" value="com.dong.web.mapper.UserMapper" />
</bean>

<!-- Inject mapper in service layer -->
<bean id="userService" class="com.dong.web.service.UserServiceImpl">
    <property name="userMapper" ref="userMapper" />
</bean>
public class UserServiceImpl implements UserService {

  private UserMapper userMapper;

  public void setUserMapper(UserMapper userMapper) {
    this.userMapper = userMapper;
  }

  public List<User> getAllUsers() {
    return this.userMapper.getAllUsers();
  }
}

MapperScannerConfigurer

Với MapperFactoryBean, chúng ta cần khai báo một bean cho mỗi mapper interface. Do đó cách tốt hơn là dùng MapperScannerConfigurer để tự động quét và tìm kiếm mapper interfaces và khai báo chúng như là MapperFactoryBean.

<!-- Scan all the interfaces under mapper/ -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
   <property name="basePackage" value="com.dong.web.mapper" />
</bean>

Chú ý: IDE sẽ đề cập đến Không thể autowired, bởi vì mỗi MapperFactoryBean được tạo ra bởi MapperScannerConfigurer, IDE không thể tìm ra một thực thể hiện tại.