关于mybatis(不断更新中)

mybatis 是一个优秀的基于 java 的持久层框架,它内部封装了 jdbc,使开发者只需要关注 sql 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。
mybatis 通过 xml 或注解的方式将要执行的各种 statement 配置起来,并通过 java 对象和 statement 中sql 的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回。
采用 ORM 思想解决了实体和数据库映射的问题,对 jdbc 进行了封装,屏蔽了 jdbc api 底层访问细节,使我们不用与 jdbc api 打交道,就可以完成对数据库的持久化操作。

在本篇文章中,除了一些通用的概念外,在实现上主要采用“注解开发”的方式呈现。

使用mybatis注解完成基本的CRUD

编写实体类

1
2
3
4
5
6
7
8
9
10
11
package com.sosactwt.forbatis;

import java.io.Serializable;

@Data
public class UserPo implements Serializable {
private Integer id;
private String username;
private String address;
private String sex;
}

编写持久层接口

1
2
3
4
5
6
7
8
9
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
52
53
54
55
56
57
58
59
60
61
package com.sosactwt.formybatis;

import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

import java.util.List;

public interface UserMapper extends BaseMapper<UserPo>{
/**
* 查询所有用户
* @return
*/
@Select("select * from user")
List<User> findAll();

/**
* 插入用户
* @param user
*/
@Insert("insert into user(username,address,sex,birthday) values(#{username},#{address},#{sex},#{birthday})")
void saveUser(User user);

/**
* 更新用户
* @param user
*/
@Update("update user set username=#{username},sex=#{sex},birthday=#{birthday},address=#{address} where id=#{id}")
void updateUser(User user);

/**
* 删除用户
* @param userId
*/
@Delete("delete from user where id=#{userId}")
void deleteUser(Integer userId);
/**
* 根据id查询用户
* @param userId
* @return
*/
@Select("select * from user where id=#{userId}")
User findByUserId(Integer userId);

/**
* 根据用户名称模糊查询
* @param username
* @return
*/
//@Select("select * from user where username like #{username}") //占位符
@Select("select * from user where username like '%${value}%'") //字符串拼接
List<User> findByName(String username);

/**
* 查询总数量
* @return
*/
@Select("select count(*) from user")
int findTotal();
}

如此之后,我们可以用JUnit来做一下简单的单元测试,以对findById()的测试举例:

虽然我们这里只测试了一个,但是当你测试多个的时候,为了避免需要反复初始化与销毁,我们往往会用到@BeforeEach@AfterEach,在每个@Test之前和之后他俩都会运行一次。(具体可以在JUnit那篇里面看看)

1
2
3
4
5
6
7
8
9
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
package com.sosactwt.formybatis;

public class UserMapperTest extends ApplicationTest{
@Autowired
private UserMapper userMapper;
@Autowired
private JdbcTemplate jdbcTemplate;

@BeforeEach
public void runBeforeEachTest(){
UserPo userPo = new UserPo(3,"sosactwt","China","male");
userMapper.insert(userPo);//插入数据

}
@AfterEach
public void runAfterEachTest(){
jdbcTemplate.update("delete from user");
}
@Test
public void findByUserIdTest(){
Integer userId = 1;
User result = usermapper.findById(userId);
assertEquals(UserId,result.getId(),"失败啦!");
//如果查找成功,则会显示测试pass,否则就会告诉你“失败啦!”
}

private UserPo createUserPo(
Integer id;
String username;
String address;
String sex;){

UserPo userPo = new UserPo;
userPo.setId(id);
userPo.setUsername(username);
userPo.setAddress(address);
userPo.setSex(sex);
return userPo;
}
}

处理更加复杂一点的关系映射-多表查询

除了上面简单的CRUD之外,我们还会遇到一些复杂一些的关系映射。

我们将用到**@Results 注解,@Result 注解,@One 注解,@Many 注解。**

现在来解释一下各个注解的意思:

@Results 注解
代替的是标签<resultMap>(与xml相比)
该注解中可以使用单个@Result 注解,也可以使用@Result 集合
@Results({@Result(),@Result()})@Results(@Result())

@Resut 注解
代替了<id> 标签和<result>标签(与xml相比)
@Result 中的属性介绍:

@Result 中的属性 介绍
id 是否是主键字段
column 数据库的列名
property 需要装配的属性名
one 需要使用的@One 注解(@Result(one=@One)()))
many 需要使用的@Many 注解(@Result(many=@many)()))

现在我们设想一个需求:加载账户信息并且加载该账户的用户信息,根据情况可实现立即加载

为了展示实例,我们先建一个“账户实体类”AccountPo,配合前面的UserPo。

1
2
3
4
5
6
7
8
9
10
11
package com.sosactwt.forbatis;

import java.io.Serializable;

@Data
public class AccountPo implements Serializable {
private Integer id;
private Integer uid;
private double money;
private User user;//常识可知,账户和用户是多对一的映射(mybatis中称之为一对一,一个账户只能属于一个用户)
}

@One 注解(一对一)
代替了<association>标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。
@One注解属性介绍:
select 指定用来多表查询的 sqlmapper
fetchType 会覆盖全局的配置参数 lazyLoadingEnabled。
使用格式:@Result(column=" “,property=”",one=@One(select=""))

AccountMapper:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface AccountMapper {

/**
* 查询所有账户,并且获取每个账户下的用户信息,一对一
* @return
*/
@Select("select * from account")
@Results(id="accountMap",value = {
@Result(id = true,column = "id",property = "id"),
@Result(column = "uid",property = "uid"),
@Result(column = "money",property = "money"),
@Result(property = "user",column = "uid",
one=@One(select="com.sosactwt.formybatis.UserMapper.findById",fetchType= FetchType.EAGER))//即一对一查找,且为立即加载
})
List<Account> findAll();

UserMapper:

1
2
3
4
5
6
7
8
9
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
public interface UserMapper {

/**
* 查询所有用户
* @return
*/
@Select("select * from user")
@Results(id="userMap",value={
@Result(id = true,column = "id",property = "userId"),
@Result(column = "id",property = "userId"),
@Result(column = "username",property = "userName"),
@Result(column = "sex",property = "userSex")
})
List<User> findAll();

/**
* 根据id查询用户
* @param userId
* @return
*/
@Select("select * from user where id=#{id}")
//@ResultMap(value={"userMap"})
@ResultMap("userMap")
User findById(Integer userId);

/**
* 根据用户名称模糊查询
* @param username
* @return
*/
@Select("select * from user where username like #{username}") //占位符
@ResultMap("userMap")
List<User> findByName(String username);

}

现在我们考虑另一个需求:

需求:查询用户信息时,也要查询他的账户列表。使用注解方式实现。并设置延时加载。
分析:一个用户具有多个账户信息,所以形成了用户(User)与账户(Account)之间的一对多关系。

@Many 注解(一对多)

代替了<collection>标签,是是多表查询的关键,在注解中用来指定子查询返回对象集合。
注意:聚集元素用来处理“一对多”的关系。需要指定映射的 Java 实体类的属性,属性的 javaType(一般为 ArrayList)但是注解中可以不定义。
使用格式:@Result(property="",column="",many=@Many(select=""))

由于我们要在查User的时候查到多个Account,故我们考虑重写一个UserPo,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.sosactwt.forbatis;

import java.io.Serializable;

@Data
public class UserPo implements Serializable {
private Integer id;
private String username;
private String address;
private String sex;

//一对多关系映射:一个用户对应多个账户
private List<AccountPo> accountPos;
}

然后开始写Mapper

UserMapper:

1
2
3
4
5
6
7
8
9
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
public interface UserMapper {

/**
* 查询所有用户
* @return
*/
@Select("select * from user")
@Results(id="userMap",value={
@Result(id = true,column = "id",property = "userId"),
@Result(column = "id",property = "userId"),
@Result(column = "username",property = "userName"),
@Result(column = "sex",property = "userSex"),
@Result(column = "birthday",property = "userBirthday"),
@Result(property = "accounts" ,column = "id",
many = @Many(select = "com.keafmd.dao.IAccountDao.findAccountByUid",
fetchType = FetchType.LAZY))

})
List<User> findAll();
/**
* 根据id查询用户
* @param userId
* @return
*/
@Select("select * from user where id=#{userId}")
//@ResultMap(value={"userMap"})
@ResultMap("userMap")
User findById(Integer userId);

/**
* 根据用户名称模糊查询
* @param username
* @return
*/
@Select("select * from user where username like #{username}") //占位符
@ResultMap("userMap")
List<User> findByName(String username);


}

AccountMapper:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public interface AccountMapper {

/**
* 查询所有账户,并且获取每个账户下的用户信息,一对一 ,* 这里用不到这个findAll()
* @return
*/
@Select("select * from account")
@Results(id="accountMap",value = {
@Result(id = true,column = "id",property = "id"),
@Result(column = "uid",property = "uid"),
@Result(column = "money",property = "money"),
@Result(property = "user",column = "uid",one=@One(select="com.keafmd.dao.IUserDao.findById",fetchType= FetchType.EAGER))
})
List<Account> findAll();


/**
* 根据用户id查询账户信息
* @param userId
* @return
*/
@Select("select * from account where uid = #{userId}")
List<Account> findAccountByUid(Integer userId);

}