mybatis3入门学习

一直使用JdbcTemplate开发项目,优点主要是比较灵活自己可以随便写sql都行,不受任何框架的限制。但缺点也比较明显,直接在代码里面拼接SQL只适合小团队和小项目开发,在多人协作或代码量较多的项目中会对后期维护造成很大的困难。

选择mybatis也是因为这个框架对JDBC的封装不像Hibernate那么重,使用这个框架可以统一管理SQL又能利用JDBC的高性能。

这里也是以同Spring框架集成为例子。

例子项目结构如下:

1432630367537.jpg


SpringMVC的配置文件不再多说,下面贴出和mybatis的集成配置:

添加如下配置到applicationContext.xml中

    <!-- Spring Mybatis -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
      <property name="dataSource" ref="dataSource" />
      <!-- 
      <property name="configLocation" value="WEB-INF/mybatis-config"/>
       -->
      <property name="typeAliasesPackage" value="me.zhaojunling.demo.mybatis.model" />
      <property name="mapperLocations" value="classpath*:mapper/*.xml" />
    </bean>
    
    <bean class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>


注释掉的configLocation这行是因为使用内部测试框架的时候会找不到mybatis-config文件,如果不使用测试框架则直接配置一个configLocation就行 。

下面问题来了,如何把mybatis的Mapper和Dao关联上呢?

上UserMapper.xml代码

<?xml version="1.0" encoding="UTF-8" ?>   
<!DOCTYPE mapper   
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"   
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="me.zhaojunling.demo.mybatis.dao.UserDao">
	<select id="getUser" resultType="user">
		select * from user where id = #{id}
	</select>

	<insert id="addUser" useGeneratedKeys="true" keyProperty="id">
		insert into user (name, day) values(#{name}, #{day})
	</insert>

</mapper>

其中:useGeneratedKeys="true" keyProperty="id"的配置是插入数据库后自动填充生成的主键ID值到实体中。

上UserDao的代码

package me.zhaojunling.demo.mybatis.dao;

import me.zhaojunling.demo.mybatis.model.User;

public interface UserDao {

	public User getUser(Integer id);
	
	public User getUserWithGroup(Integer id);
	
	public void addUser(User user);
}

使用mybatis可以不用写Dao的实现类,只要提供一个接口并在Mapper中把接口类的全路径名设置为namespace的值,接口中的每个方法对应与Mapper文件中的id项。

因为Dao只是一个接口类所以不用添加@Repository注解,具体在service层如何使用,代码如下:

package me.zhaojunling.demo.mybatis.service;

import me.zhaojunling.demo.mybatis.dao.GroupDao;
import me.zhaojunling.demo.mybatis.dao.UserDao;
import me.zhaojunling.demo.mybatis.model.Group;
import me.zhaojunling.demo.mybatis.model.User;

import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {

	@Autowired
	private SqlSessionTemplate sqlSessionTemplate;
	
	public User getUser(Integer id){
		return sqlSessionTemplate.getMapper(UserDao.class).getUser(id);
	}
	public void addUser(User user){
		sqlSessionTemplate.getMapper(UserDao.class).addUser(user);
	}
}

这样就基本完成了集成mybatis到Spring中的工作。

下面要说一下可能会经常用到的功能,比如一对多或者多对一的关联查询,有点像Hibernate的关联映射。

先说下关联关系:

表user,表group:,关联字段设置在user中字段名为group_id,对应group表的主键


user表包含字段group_id,对应group的主键

User类添加属性group,类型是Group

Group类添加属性userList, 类型是List<User>

下面贴一下User中如果关联查询出Group的配置方法:

UserMapper.xml

	<select id="getUserWithGroup" resultMap="userWithGroup">
		select * from user where id = #{id}
	</select>

	<resultMap type="user" id="userWithGroup">
	    <association property="group" column="group_id" javaType="group" select="selectGroup" />
	</resultMap>

	<select id="selectGroup" resultType="group">
		select * from `group` where id = #{id}
	</select>

这里以UserDao中的getUserWithGroup方法为例,需要注意的有下面几点:

1.使用自定一的resultMap时,在select的标签部分需要使用resultMap声明返回类型,具体select中resultMap属性的值就是自定义的resultMap的id。

2.使用association标签,配置关联查询关系。

3.property="group"表示User类的group属性名称,column="group_id则表示user表中的group_id字段,javaType="group"即使关联的实体类性

4.select="selectGroup"表示关联实体表的查询,每查询出一个User数据后会根据这里的配置再次查询出一个group对象,查询的id参数值就是上面配置的group_id。

执行时的log日志如下:

DEBUG JakartaCommonsLoggingImpl.java:49 - ==>  Preparing: select * from user where id = ? 

DEBUG JakartaCommonsLoggingImpl.java:49 - ==> Parameters: 1(Integer)

DEBUG JakartaCommonsLoggingImpl.java:49 - ====>  Preparing: select * from `group` where id = ? 

DEBUG JakartaCommonsLoggingImpl.java:49 - ====> Parameters: 1(Integer)

DEBUG JakartaCommonsLoggingImpl.java:49 - <====      Total: 1

DEBUG JakartaCommonsLoggingImpl.java:49 - <==      Total: 1

和Hibernate的一样,也会有N+1查询的问题,所以当查询结果过多或者并发比较大的情况下使用时可能会有性能问题。


通过Group关联查询出User列表的配置稍微有些不同。

GroupMapper.xml

    <select id="getGroup" resultMap="groupWithUsers">
        select * from `group` where id = #{id}
    </select>
    
    <resultMap type="group" id="groupWithUsers">
            <result column="id" property="id"/>
        <collection property="userList" javaType="ArrayList" column="id"  ofType="user" select="selectUser"/>
    </resultMap>
    
    <select id="selectUser" resultType="user">
        select * from user where group_id = #{id}
    </select>

和User关联查询出Group的配置不同点是这里使用了collection标签进行关联。

这里需要注意的则是ofType="user",这个地方需要说明列表里面的具体类型,这里因为查询的是userList,所以设置为user。

同上面的一样property="userList"表示Group的属性名称,column="id"表示使用id的值作为关联查询的参数。

select="selectUser"则表示使用id为selectUser的查询配置,可以看到在selectUser里面查询的条件是group_id。

也就是查询出一条Group记录后,把Group的id值作为参数执行selectUser的语句,查询出符合条件的User列表。

执行时的log如下:

DEBUG JakartaCommonsLoggingImpl.java:49 - ==>  Preparing: select * from `group` where id = ? 

DEBUG JakartaCommonsLoggingImpl.java:49 - ==> Parameters: 1(Integer)

DEBUG JakartaCommonsLoggingImpl.java:49 - ====>  Preparing: select * from user where group_id = ? 

DEBUG JakartaCommonsLoggingImpl.java:49 - ====> Parameters: 1(Integer)

DEBUG JakartaCommonsLoggingImpl.java:49 - <====      Total: 4

DEBUG JakartaCommonsLoggingImpl.java:49 - <==      Total: 1


最后是Controller和测试的代码

DemoController

package me.zhaojunling.demo.mybatis.controller;

import java.util.Date;

import me.zhaojunling.demo.mybatis.model.User;
import me.zhaojunling.demo.mybatis.service.UserService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class DemoController {

	@Autowired
	UserService userService;

	@RequestMapping("/getUser")
	@ResponseBody
	public Object getUser(@RequestParam(required = false, defaultValue = "0") Integer id) {
		return userService.getUser(id);
	}
	
	@RequestMapping("/getUserWithGroup")
	@ResponseBody
	public Object getUserWithGroup(@RequestParam(required = false, defaultValue = "0") Integer id) {
		return userService.getUserWithGroup(id);
	}
	
	@RequestMapping("/addUser")
	@ResponseBody
	public Object addUser(@RequestParam(required = false, defaultValue = "") String name) {
		User user = new User();
		user.setName(name);
		user.setDay(new Date());
		userService.addUser(user);
		return user;
	}
	
	@RequestMapping("/getGroup")
	@ResponseBody
	public Object getGroup(@RequestParam(required = false, defaultValue = "0") Integer id) {
		return userService.getGroup(id);
	}
}

DemoControllerTest

package mybatis;

import org.junit.Before;
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 org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;

import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/applicationContext.xml", "file:src/main/webapp/WEB-INF/servlet-context.xml" })
public class DemoControllerTest {

	@Autowired
	private WebApplicationContext wac;
	private MockMvc mockMvc;

	@Before
	public void setup() throws Exception {
		this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
	}
	
	@Test
	public void getUserTest() throws Exception {
		mockMvc.perform(get("/getUser?id={id}", "1"))
		.andDo(print())
		.andExpect(status().isOk())
		.andExpect(jsonPath("$.id").value(1))
		; 
	}
	
	@Test
	public void getUserWithGroupTest() throws Exception {
		mockMvc.perform(get("/getUserWithGroup?id={id}", "1"))
		.andDo(print())
		.andExpect(status().isOk())
		.andExpect(jsonPath("$.group.name").value("测试分组"))
		;
	}
	
	@Test
	public void addUserTest() throws Exception {
		mockMvc.perform(get("/addUser?name={name}", "myname"))
		.andDo(print())
		.andExpect(status().isOk())
		.andExpect(jsonPath("$.name").value("myname"))
		; 
	}
	
	@Test
	public void getGroupTest() throws Exception {
		mockMvc.perform(get("/getGroup?id={id}", "1"))
		.andDo(print())
		.andExpect(status().isOk())
		.andExpect(jsonPath("$.userList").isArray())
		; 
	}
	
}


mybatis的结果集映射还有很多高级用法,参考地址:

https://mybatis.github.io/mybatis-3/zh/sqlmap-xml.html


另:比较复杂的查询结果集是否直接使用map作为返回类型比较方便,这个只能等以后去验证了。

提交评论