Для иллюстрации мы рассматриваем следующую примерную модель предметной области:
Будут пользователи, и у каждого пользователя может быть блог, и каждый блог может содержать ноль или более постов.
Структура базы данных трех таблиц выглядит следующим образом:
|
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@Transactionalpublic 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@Transactionalpublic 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 Сивы Редди в блоге « Мои эксперименты по технологии» .