Статьи

Учебное пособие по MyBatis — Операции CRUD и отображения отношений — Часть 2

Для иллюстрации мы рассматриваем следующую примерную модель предметной области:

Будут пользователи, и у каждого пользователя может быть блог, и каждый блог может содержать ноль или более постов.

Структура базы данных трех таблиц выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
CREATE TABLE user (
  user_id int(10) unsigned NOT NULL auto_increment,
  email_id varchar(45) NOT NULL,
  password varchar(45) NOT NULL,
  first_name varchar(45) NOT NULL,
  last_name varchar(45) default NULL,
  blog_id int(10) unsigned default NULL,
  PRIMARY KEY  (user_id),
  UNIQUE KEY Index_2_email_uniq (email_id),
  KEY FK_user_blog (blog_id),
  CONSTRAINT FK_user_blog FOREIGN KEY (blog_id) REFERENCES blog (blog_id)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
1
2
3
4
5
6
CREATE TABLE blog (
  blog_id int(10) unsigned NOT NULL auto_increment,
  blog_name varchar(45) NOT NULL,
  created_on datetime NOT NULL,
  PRIMARY KEY  (blog_id)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
01
02
03
04
05
06
07
08
09
10
CREATE TABLE post (
  post_id int(10) unsigned NOT NULL auto_increment,
  title varchar(45) NOT NULL,
  content varchar(1024) NOT NULL,
  created_on varchar(45) NOT NULL,
  blog_id int(10) unsigned NOT NULL,
  PRIMARY KEY  (post_id),
  KEY FK_post_blog (blog_id),
  CONSTRAINT FK_post_blog FOREIGN KEY (blog_id) REFERENCES blog (blog_id)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Здесь я собираюсь объяснить, как получить и отобразить сопоставления результатов * -has-One и One-to-Many.

01
02
03
04
05
06
07
08
09
10
11
12
package com.sivalabs.mybatisdemo.domain;
 
public class User
{
 private Integer userId;
 private String emailId;
 private String password;
 private String firstName;
 private String lastName;
 private Blog blog;
 //setters and getters
}

01
02
03
04
05
06
07
08
09
10
11
12
13
14
package com.sivalabs.mybatisdemo.domain;
 
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
 
public class Blog {
 
 private Integer blogId;
 private String blogName;
 private Date createdOn;
 private List<Post> posts = new ArrayList<Post>();
 //setters and getters
}

01
02
03
04
05
06
07
08
09
10
11
12
package com.sivalabs.mybatisdemo.domain;
 
import java.util.Date;
 
public class Post
{
 private Integer postId;
 private String title;
 private String content;
 private Date createdOn;
 //setters and getters
}

В mybatis-config.xml настройте псевдонимы типов для bean-компонентов.

1
2
3
4
5
<typeAliases>
  <typeAlias type='com.sivalabs.mybatisdemo.domain.User' alias='User'/>
  <typeAlias type='com.sivalabs.mybatisdemo.domain.Blog' alias='Blog'/>
  <typeAlias type='com.sivalabs.mybatisdemo.domain.Post' alias='Post'/> 
</typeAliases>

* -Has-One Result Mapping:

В UserMapper.xml настройте SQL-запросы и сопоставьте результаты следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<mapper namespace='com.sivalabs.mybatisdemo.mappers.UserMapper'>
 
 <resultMap type='User' id='UserResult'>
    <id property='userId' column='user_id'/>
    <result property='emailId' column='email_id'/>
    <result property='password' column='password'/>
    <result property='firstName' column='first_name'/>
    <result property='lastName' column='last_name'/>
    <association property='blog' resultMap='BlogResult'/>
   </resultMap>
 
 <resultMap type='Blog' id='BlogResult'>
    <id property='blogId' column='blog_id'/>
    <result property='blogName' column='BLOG_NAME'/>
    <result property='createdOn' column='CREATED_ON'/>   
   </resultMap>
 
  <select id='getUserById' parameterType='int' resultMap='UserResult'>
 
     SELECT
      U.USER_ID, U.EMAIL_ID, U.PASSWORD, U.FIRST_NAME, U.LAST_NAME,
      B.BLOG_ID, B.BLOG_NAME, B.CREATED_ON
  FROM USER U LEFT OUTER JOIN BLOG B ON U.BLOG_ID=B.BLOG_ID
  WHERE U.USER_ID = #{userId}
  </select>
 
  <select id='getAllUsers' resultMap='UserResult'>
   SELECT
     U.USER_ID, U.EMAIL_ID, U.PASSWORD, U.FIRST_NAME, U.LAST_NAME,
     B.BLOG_ID, B.BLOG_NAME, B.CREATED_ON
 FROM USER U LEFT OUTER JOIN BLOG B ON U.BLOG_ID=B.BLOG_ID
  </select>
 
</mapper>

В тесте JUnit напишите метод для проверки загрузки ассоциации.

01
02
03
04
05
06
07
08
09
10
11
public void getUserById()
{
 SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
 try{
  UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  User user = userMapper.getUserById(1);
  System.out.println(user.getBlog());
 }finally{
  sqlSession.close();
 }
}

Отображение результатов «один ко многим»:

В BlogMapper.xml настройте связь между блогами и сообщениями следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<mapper namespace='com.sivalabs.mybatisdemo.mappers.BlogMapper'>
 
 <resultMap type='Blog' id='BlogResult'>
    <id property='blogId' column='blog_id'/>
    <result property='blogName' column='BLOG_NAME'/>
    <result property='createdOn' column='CREATED_ON'/>
    <collection property='posts' ofType='Post' resultMap='PostResult' columnPrefix='post_'></collection>
   </resultMap>
 
   <resultMap type='Post' id='PostResult'>
    <id property='postId' column='post_id'/>
    <result property='title' column='title'/>
    <result property='content' column='content'/>
    <result property='createdOn' column='created_on'/>
   </resultMap>
 
  <select id='getBlogById' parameterType='int' resultMap='BlogResult'>
 
     SELECT
      b.blog_id, b.blog_name, b.created_on,
      p.post_id as post_post_id, p.title as post_title, p.content as post_content, p.created_on as post_created_on
  FROM blog b left outer join post p on b.blog_id=p.blog_id
     WHERE b.BLOG_ID=#{blogId}
  </select>
 
  <select id='getAllBlogs' resultMap='BlogResult'>
   SELECT
    b.blog_id, b.blog_name, b.created_on as blog_created_on,
     p.post_id as post_post_id, p.title as post_title, p.content as post_content, p.created_on as post_created_on
 FROM blog b left outer join post p on b.blog_id=p.blog_id
  </select>
 
</mapper>

В JUnit Test напишите тестовый метод для проверки соответствия между блогами и публикациями.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public void getBlogById()
{
 SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
 try{
 BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
 Blog blog = blogMapper.getBlogById(1);
 System.out.println(blog);
 List<Post> posts = blog.getPosts();
 for (Post post : posts) {
  System.out.println(post);
 }
 }finally{
  sqlSession.close();
 }
}

S pring Интеграция

MyBatis-Spring является подпроектом MyBatis и предоставляет поддержку интеграции Spring, которая значительно упрощает использование MyBatis. Для тех, кто знаком с Spring в процессе внедрения зависимостей, использование MyBatis-Spring очень просто.

Сначала давайте рассмотрим процесс использования MyBatis без Spring.

1. Создайте SqlSessionFactory, используя SqlSessionFactoryBuilder, передав mybatis-config.xml, который содержит свойства DataSource, XML-списки Mapper, TypeAliases и т. Д.

2. Создайте объект SqlSession из SqlSessionFactory

3. Получить экземпляр Mapper из SqlSession и выполнить запросы.

4. Подтвердите или откатите транзакцию, используя объект SqlSession.

С MyBatis-Spring большинство вышеперечисленных шагов можно настроить в Spring ApplicationContext, а экземпляры SqlSession или Mapper могут быть внедрены в Spring Beans. Затем мы можем использовать функции Spring TransactionManagement без написания кода фиксации / отката транзакции по всему коду.

Теперь давайте посмотрим, как мы можем настроить интеграцию с MyBatis + Spring.

Шаг № 1: Настройте зависимости MyBatis-Spring в pom.xml

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.10</version>
    <scope>test</scope>
</dependency>
 
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.1.1</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.1.1</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>3.1.1.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>3.1.1.RELEASE</version>
    <scope>test</scope>
</dependency>
<dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.21</version>
        <scope>runtime</scope>
    </dependency>
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib-nodep</artifactId>
    <version>2.2.2</version>
</dependency>

Шаг № 2: Вам не нужно настраивать свойства базы данных в mybatis-config.xml.

Мы можем настроить DataSource в Spring Container и использовать его для сборки MyBatis SqlSessionFactory.

Вместо SqlSessionFactoryBuilder MyBatis-Spring использует org.mybatis.spring.SqlSessionFactoryBean для построения SqlSessionFactory.

Мы можем передать dataSource, расположение файлов XML Mapper, typeAliases и т. Д. В SqlSessionFactoryBean.

01
02
03
04
05
06
07
08
09
10
11
12
<bean id='dataSource' class='org.apache.commons.dbcp.BasicDataSource'>
    <property name='driverClassName' value='${jdbc.driverClassName}'/>
    <property name='url' value='${jdbc.url}'/>
    <property name='username' value='${jdbc.username}'/>
    <property name='password' value='${jdbc.password}'/>
</bean>
 
<bean id='sqlSessionFactory' class='org.mybatis.spring.SqlSessionFactoryBean'>
    <property name='dataSource' ref='dataSource' />
    <property name='typeAliasesPackage' value='com.sivalabs.mybatisdemo.domain'/>
    <property name='mapperLocations' value='classpath*:com/sivalabs/mybatisdemo/mappers/**/*.xml' />
</bean>

Шаг № 3: Настройте SqlSessionTemplate, который предоставляет объект ThreadSafe SqlSession.

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

Шаг № 4: Чтобы иметь возможность внедрять Mappers напрямую, мы должны зарегистрировать org.mybatis.spring.mapper.MapperScannerConfigurer и настроить имя пакета, где найти интерфейсы Mapper.

1
2
3
<bean class='org.mybatis.spring.mapper.MapperScannerConfigurer'>
  <property name='basePackage' value='com.sivalabs.mybatisdemo.mappers' />
</bean>

Шаг № 5: Настройте TransactionManager для поддержки аннотации на основе транзакций.

1
2
3
4
5
6
<tx:annotation-driven transaction-manager='transactionManager'/>
 
<bean id='transactionManager' class='org.springframework.jdbc.datasource.DataSourceTransactionManager'>
    <property name='dataSource' ref='dataSource' />
</bean>

Шаг № 6: Обновите классы Service и зарегистрируйте их в контейнере Spring.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.sivalabs.mybatisdemo.service;
 
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
 
import com.sivalabs.mybatisdemo.domain.User;
import com.sivalabs.mybatisdemo.mappers.UserMapper;
 
@Service
@Transactional
public class UserService
{
    @Autowired
    private SqlSession sqlSession; //This is to demonstrate injecting SqlSession object
 
    public void insertUser(User user)
    {
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        userMapper.insertUser(user);
    }
 
    public User getUserById(Integer userId)
    {
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        return userMapper.getUserById(userId);
    }
 
}

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.sivalabs.mybatisdemo.service;
 
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.sivalabs.mybatisdemo.domain.Blog;
import com.sivalabs.mybatisdemo.mappers.BlogMapper;
 
@Service
@Transactional
public class BlogService
{
    @Autowired
    private BlogMapper blogMapper; // This is to demonstratee how to inject Mappers directly
 
    public void insertBlog(Blog blog) {
        blogMapper.insertBlog(blog);
    }
 
    public Blog getBlogById(Integer blogId) {
        return blogMapper.getBlogById(blogId);
    }
 
    public List<Blog> getAllBlogs() {
        return blogMapper.getAllBlogs();
    }
}

Примечание. Если мы можем напрямую внедрить Mappers, тогда зачем нам вводить объекты SqlSession? Потому что объект SqlSession содержит более тонкий метод, который иногда оказывается полезным.

Например: если мы хотим узнать, сколько записей было обновлено с помощью запроса на обновление, мы можем использовать SqlSession следующим образом:

1
int updatedRowCount = sqlSession.update('com.sivalabs.mybatisdemo.mappers.UserMapper.updateUser', user);

До сих пор я не нашел способ получить количество обновлений строк без использования объекта SqlSession.

Шаг # 7 Напишите тесты JUnit для тестирования UserService и BlogService.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.sivalabs.mybatisdemo;
 
import java.util.List;
 
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 
import com.sivalabs.mybatisdemo.domain.User;
import com.sivalabs.mybatisdemo.service.UserService;
 
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations='classpath:applicationContext.xml')
public class SpringUserServiceTest
{
    @Autowired
    private UserService userService;
 
    @Test
    public void testGetUserById()
    {
        User user = userService.getUserById(1);
        Assert.assertNotNull(user);
        System.out.println(user);
        System.out.println(user.getBlog());
    }
 
    @Test
    public void testUpdateUser()
    {
        long timestamp = System.currentTimeMillis();
        User user = userService.getUserById(2);
        user.setFirstName('TestFirstName'+timestamp);
        user.setLastName('TestLastName'+timestamp);
        userService.updateUser(user);
        User updatedUser = userService.getUserById(2);
        Assert.assertEquals(user.getFirstName(), updatedUser.getFirstName());
        Assert.assertEquals(user.getLastName(), updatedUser.getLastName());
    }
 
}

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.sivalabs.mybatisdemo;
 
import java.util.Date;
import java.util.List;
 
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 
import com.sivalabs.mybatisdemo.domain.Blog;
import com.sivalabs.mybatisdemo.domain.Post;
import com.sivalabs.mybatisdemo.service.BlogService;
 
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations='classpath:applicationContext.xml')
public class SpringBlogServiceTest
{
    @Autowired
    private BlogService blogService;
 
    @Test
    public void testGetBlogById()
    {
        Blog blog = blogService.getBlogById(1);
        Assert.assertNotNull(blog);
        System.out.println(blog);
        List<Post> posts = blog.getPosts();
        for (Post post : posts) {
            System.out.println(post);
        }
    }
 
    @Test
    public void testInsertBlog()
    {
        Blog blog = new Blog();
        blog.setBlogName('test_blog_'+System.currentTimeMillis());
        blog.setCreatedOn(new Date());
 
        blogService.insertBlog(blog);
        Assert.assertTrue(blog.getBlogId() != 0);
        Blog createdBlog = blogService.getBlogById(blog.getBlogId());
        Assert.assertNotNull(createdBlog);
        Assert.assertEquals(blog.getBlogName(), createdBlog.getBlogName());
 
    }
 
}

Ссылка: Учебник MyBatis: Часть 3 — Отображение отношений ,   Учебное пособие по MyBatis: часть 4 — Spring Integration от нашего партнера по JCG Сивы Редди в блоге « Мои эксперименты по технологии» .