MyBatis —— 多表操作和注解开发
一、前言
本文需要导入的坐标如下:
<dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.1</version><scope>test</scope></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.32</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.6</version></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.12</version></dependency><dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>3.7.5</version></dependency><dependency><groupId>com.github.jsqlparser</groupId><artifactId>jsqlparser</artifactId><version>0.9.1</version></dependency></dependencies>
核心配置文件如下:
<configuration><!--通过properties标签加载外部properties文件--><properties resource="jdbc.properties"/><!--取别名--><typeAliases><typeAlias type="com.yds.domain.User" alias="user"/><typeAlias type="com.yds.domain.Order" alias="order"/><typeAlias type="com.yds.domain.Role" alias="role"/></typeAliases><!--配置数据源环境--><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></dataSource></environment></environments><!--加载映射文件--><mappers><mapper resource="com.yds.mapper/UserMapper.xml"/><mapper resource="com.yds.mapper/OrderMapper.xml"/></mappers></configuration>
二、多表操作
首先我们需要创建四个表,分别是user、role、orders、user_role。
user表:
orders表:
role表:
user_role表(中间表):
然后需要创建对应的实体pojo:
User类:
public class User {private int id;private String username;private String password;private Date birthday;//描述的是当前用户存在哪些订单private List<Order> orderList;//描述的是当前用户均被哪些角色private List<Role> roleList;public List<Role> getRoleList() {return roleList;}public void setRoleList(List<Role> roleList) {this.roleList = roleList;}public List<Order> getOrderList() {return orderList;}public void setOrderList(List<Order> orderList) {this.orderList = orderList;}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public Date getBirthday() {return birthday;}public void setBirthday(Date birthday) {this.birthday = birthday;}@Overridepublic String toString() {return "User{" +"id=" + id +", username='" + username + '\'' +", password='" + password + '\'' +", birthday=" + birthday +", orderList=" + orderList +", roleList=" + roleList +'}';}
}
Oder类:
public class Order {private int id;private Date ordertime;private double total;//当前订单属于哪一个用户private User user;public int getId() {return id;}public void setId(int id) {this.id = id;}public Date getOrderTime() {return ordertime;}public void setOrderTime(Date orderTime) {this.ordertime = orderTime;}public double getTotal() {return total;}public void setTotal(double total) {this.total = total;}public User getUser() {return user;}public void setUser(User user) {this.user = user;}@Overridepublic String toString() {return "Order{" +"id=" + id +", orderTime=" + ordertime +", total=" + total +", user=" + user +'}';}
}
Role类:
public class Role {private int id;private String roleName;private String roleDesc;public int getId() {return id;}public void setId(int id) {this.id = id;}public String getRoleName() {return roleName;}public void setRoleName(String roleName) {this.roleName = roleName;}public String getRoleDesc() {return roleDesc;}public void setRoleDesc(String roleDesc) {this.roleDesc = roleDesc;}@Overridepublic String toString() {return "Role{" +"id=" + id +", roleName='" + roleName + '\'' +", roleDesc='" + roleDesc + '\'' +'}';}
}
1.一对一查询
我们设置一个情景:一个用户(user)拥有几个订单(order),一个订单会被一个用户购买。
现在我们希望查询每个订单的所属情况(订单信息和购买的用户信息)
显然的我们应该在映射文件中配置:
<select id="findAll" resultMap="userMap">select *,o.id oid from user u,orders o where u.id=o.uid
</select>
同时为了展示Order中的user的数据,还需要配置结果集:
<mapper namespace="com.yds.mapper.OrderMapper"><resultMap id="orderMap" type="order"><!--手动指定字段与实体属性的映射关系column:数据表的字段名称property:实体的属性名称--><id column="oid" property="id"/><result column="ordertime" property="ordertime"/><result column="total" property="total"/><result column="uid" property="user.id"/>--><result column="username" property="user.username"/>--><result column="password" property="user.password"/>--><result column="birthday" property="user.birthday"/>--></resultMap>
根据配置,Order的映射接口应该设置如下:
public interface OrderMapper {public List<Order> findAll();}
执行测试方法:
@Testpublic void test1() throws IOException {InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);SqlSession sqlSession = sqlSessionFactory.openSession();OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);List<Order> orderList = mapper.findAll();for (Order order : orderList) {System.out.println(order);}sqlSession.commit();sqlSession.close();}
结果如下:
尽管结果相同,但是在映射文件中,这样无法体现user是一个其他的类,所以我们这里引入一个标签association,这个标签可以通过配置property和javaType来直接表示连接对象:
<!--这里使用association标签,直接表示要通过uid连接对应user中的主键id,并显示user中的各个字段的数据property:当前实体中的属性名称(private User user)javaType:当前实体中的属性的类型(User)--><association property="user" javaType="user"><id column="uid" property="id"/><result column="username" property="username"/><result column="password" property="password"/><result column="birthday" property="birthday"/></association>
2.一对多查询
第二个情景:现在我们想查询每个用户的订单情况
和一对一类似,我们配置映射文件如下:
<mapper namespace="com.yds.mapper.UserMapper"><resultMap id="userMap" type="user"><id column="uid" property="id"/><result column="username" property="username"/><result column="password" property="password"/><result column="birthday" property="birthday"/><!--配置集合信息property:集合名称ofType:当前集合中的数据类型--><collection property="orderList" ofType="order"><!--封装order的数据--><id column="oid" property="id"/><result column="ordertime" property="ordertime"/><result column="total" property="total"/></collection></resultMap><select id="findAll" resultMap="userMap">select *,o.id oid from user u,orders o where u.id=o.uid</select>
这里和一对一唯一不一样的地方在于:因为一个用户有很多订单,所以我们需要获得order的集合orderList,而一对一的情况下,由于一个订单只属于一个用户,所以用户不需要封装成集合。
同时根据配置文件设计一个接口:
public interface UserMapper {public List<User> findAll();}
测试方法如下:
@Testpublic void test2() throws IOException {InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);SqlSession sqlSession = sqlSessionFactory.openSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);List<User> userList = mapper.findAll();for (User user : userList) {System.out.println(user);}sqlSession.commit();sqlSession.close();}
查询结果如下:
3.多对多查询
在先前我们已经创建了user表、role表和user_role中间表了,这里我们直接使用。
多对多的区别在于需要使用中间表来对应两个表中的数据,一个用户可以有多个角色,一个角色也可以有多个用户。
我们配置映射文件如下:
<resultMap id="userRoleMap" type="user"><!--user的信息--><id column="userId" property="id"/><result column="username" property="username"/><result column="password" property="password"/><result column="birthday" property="birthday"/><!--user内部的roleList信息--><collection property="roleList" ofType="role"><id column="roleId" property="id"/><result column="roleName" property="roleName"/><result column="roleDesc" property="roleDesc"/></collection></resultMap><select id="findUserAndRoleAll" resultMap="userRoleMap">select * from user u,user_role ur,role r where u.id=ur.userId and ur.roleId=r.id;</select>
同时更新user接口:
public interface UserMapper {public List<User> findAll();public List<User> findUserAndRoleAll();}
测试方法:
@Testpublic void test3() throws IOException {InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);SqlSession sqlSession = sqlSessionFactory.openSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);List<User> userAndRole = mapper.findUserAndRoleAll();for (User user : userAndRole) {System.out.println(user);}sqlSession.commit();sqlSession.close();}
这里的结果是基于user的(当然也可以基于role,但是就需要再创建一个role的接口和映射文件了):
三、注解开发
我们这里直接沿用上述pojo类和表,这也是我把注解开发和多表操作放一起的原因。
注解开发将映射文件省略,直接将sql语句添加到接口中,实现注解,简化开发。
但是别忘了注解需要被扫描,所以我们在核心配置文件中添加被扫描的包:
<!--加载映射文件(使用注解就不需要了)--><!-- <mappers><mapper resource="com.yds.mapper/UserMapper.xml"/></mappers>--><!--加载映射关系(扫描包)--><mappers><!--指定一个包下的接口--><package name="com.yds.mapper"/></mappers>
1.单表的增删查改
注解可以大幅提升单表的开发效率,这里我们在接口中添加注解:
@Insert("insert into user values (#{id}, #{username}, #{password}, #{birthday})")public void save(User user);@Update("update user set username=#{username},password=#{password} where id = #{id}")public void update(User user);@Delete("delete from user where id = #{id}")public void delete(int id);@Select("select * from user where id = #{id}")public User findById(int id);@Select("select * from user")public List<User> findAll();
sql语句就直接写在注解中就可以了。
我们这里创建一个测试类来测试增删查改:
public class MyBatisTest {private UserMapper mapper;@Beforepublic void before() throws IOException {InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);SqlSession sqlSession = sqlSessionFactory.openSession(true);mapper = sqlSession.getMapper(UserMapper.class);}@Testpublic void testSave(){User user = new User();user.setUsername("yds");user.setPassword("12345");mapper.save(user);}@Testpublic void testUpdate(){User user = new User();user.setId(11);user.setUsername("xzt");user.setPassword("1235");mapper.update(user);}@Testpublic void testDelete(){mapper.delete(11);}@Testpublic void testFindById() {User user = mapper.findById(2);System.out.println(user);}@Testpublic void testFindAll(){List<User> all = mapper.findAll();for (User user : all) {System.out.println(user);}}}
节约时间,我们就直接看看findAll即可:
值得注意的是,这里也使用了@before注解,这是junit单元测试的注解,表示测试方法初始化,在执行每个测试方法前将自动执行注解下的初始化操作,由于我们这些操作的前面几步都是相同的,所以我们只需要放到初始化中执行即可。
2.多表操作
(1)一对一
沿用订单和用户的一对一查询方式
public interface OrderMapper {@Select("select * from orders")@Results({@Result(column = "id",property = "id"),@Result(column = "ordertime",property = "ordertime"),@Result(column = "total",property = "total"),@Result(property = "user",//封装的属性名称column = "uid",//根据哪个字段取查询user表的数据javaType = UserMapper.class,//要封装的 实体类型//select属性 代表查询哪个接口的方法获得数据one = @One(select = "com.yds.mapper.UserMapper.findById"))})/*@Select("select *,o.id oid from orders o,user u where o.uid=u.id")@Results({@Result(column = "oid",property = "id"),@Result(column = "ordertime",property = "ordertime"),@Result(column = "total",property = "total"),@Result(column = "uid",property = "user.id"),@Result(column = "username",property = "user.username"),@Result(column = "password",property = "user.password")})*/public List<Order> findAll();@Select("select * from orders where uid=#{uid}")public List<Order> findByUid(int uid);}
使用results注解来配置对应的字段和外键对应的表的字段。
我们通常使用上面的语法,和标签association一样,可以明确的看到是不同的类。
使用测试类如下:
public class MyBatisMultiTest {private OrderMapper mapper;@Beforepublic void before() throws IOException {InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);SqlSession sqlSession = sqlSessionFactory.openSession(true);mapper = sqlSession.getMapper(OrderMapper.class);}@Testpublic void testFindAll(){List<Order> all = mapper.findAll();for (Order order : all) {System.out.println(order);}}
}
效果如下,和xml配置时的一样:
(2)一对多
@Select("select * from user")@Results({@Result(id=true,column = "id",property = "id"),@Result(column = "username",property = "username"),@Result(column = "password",property = "password"),@Result(property = "orderList",column = "id",javaType = List.class,many = @Many(select = "com.yds.mapper.OrderMapper.findByUid"))})public List<User> findUserAndOrderAll();
和xml一样,基于user,这是一对多查询,我们也使用@Result注解配置,最后配置的是order的结果集,由于是对多的配置,所以使用到many的参数,表示查询order表相关数据的方法,因此,我们还需要在OrderMapper中再创建一个查询方法。
@Select("select * from orders where uid=#{uid}")public List<Order> findByUid(int uid);
测试类如下:
public class MyBatisMultiTest2 {private UserMapper mapper;@Beforepublic void before() throws IOException {InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);SqlSession sqlSession = sqlSessionFactory.openSession(true);mapper = sqlSession.getMapper(UserMapper.class);}@Testpublic void testFindAll(){List<User> userAndOrderAll = mapper.findUserAndOrderAll();for (User user : userAndOrderAll) {System.out.println(user);}}}
效果如下:
(3)多对多
多对多依旧是需要一个中间表的,由于我们没有使用xml配置,这里我们还需要再创建一个RoleMapper接口来进行配置,我们依旧在注解中使用的是多对多的那条语句。
public interface RoleMapper {@Select("select * from user_role ur,role r where ur.roleId=r.id and ur.userId=#{uid}")public List<Role> findByUid(int uid);}
同时UserMapper接口也需要写:
@Select("select * from user")@Results({@Result(id = true,column = "id",property = "id"),@Result(id = true,column = "username",property = "username"),@Result(id = true,column = "password",property = "password"),@Result(property = "roleList",column = "id",javaType = List.class,many = @Many(select = "com.yds.mapper.RoleMapper.findByUid"))})public List<User> findUserAndRoleAll();
测试类如下:
public class MyBatisMultiTest3 {private UserMapper mapper;@Beforepublic void before() throws IOException {InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);SqlSession sqlSession = sqlSessionFactory.openSession(true);mapper = sqlSession.getMapper(UserMapper.class);}@Testpublic void testFindAll(){List<User> findUserAndRoleAll = mapper.findUserAndRoleAll();for (User user : findUserAndRoleAll) {System.out.println(user);}}}
效果如下: