开篇
上篇文章我们已经讲了 Mybatis 的入门以及简单的对单表进行增删改查,那么今天我们就来讲一下使用 mybatis 开发dao的两种方式以及 mysql 比较厉害的动态 sql。
利用 mybatis 开发 DAO
1. 原始的方式开发DAO
主要完成功能
-
根据用户 id 查询用户信息
-
根据用户姓名查询用户列表
-
插入用户
-
新建一个接口 User1Dao.java 和一个实现类 User1DaoImpl.java,并在 User1Dao 中添加如下方法
User1Dao.java
public interface UserDao {
//根据用户id 查询用户
User findUserById(Integer id);
//根据用户名称查询用户列表
List<User> findUserByName(String name);
//插入用户
void insertUser(User user);
}
- 在实现类中实现如下方法
User1DaoImpl.java
public class User1DaoImpl implements UserDao{
private static SqlSessionFactory factory;
static {
String resource = "SqlMapConfig.xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
factory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public User findUserById(Integer id) {
SqlSession session = factory.openSession();
User user= session.selectOne("test.findUserById", id);
return user;
}
@Override
public List<User> findUserByName(String name) {
SqlSession session = factory.openSession();
List<User> userList = session.selectList("test.findUserByName", name);
return userList;
}
@Override
public void insertUser(User user) {
SqlSession session = factory.openSession();
session.insert("test.insertUser", user);
session.commit();
}
}
注意
只有查询方法不用提交事务,其他的,比如增加,删除,更新等操作都要执行 session.close() 方法。
- 新建测试类 TestDao1.java
public class TestDao1 {
@Test
public void testFindUserById(){
System.out.println(new User1DaoImpl().findUserById(6));
}
@Test
public void testFindUserByName(){
List<User> userList = new User1DaoImpl().findUserByName("二");
for (User user :
userList) {
System.out.println(user);
}
}
@Test
public void testInsertUser(){
User user = new User();
user.setName("李二牛");
user.setAddress("湖北武汉");
user.setSex("1");
new User1DaoImpl().insertUser(user);
}
}
以上的结果就非常简单了,在这里我就不贴图了,相信小伙伴们都可以看得明白。这就是原生的 Dao 的开发方式,实际上还是有点麻烦的。这里省略了 service 层,这种原生的开发方式我们只要知道怎么写就可以了,在实际工作中我们并不会使用这种方式来开发。
2.使用 Mapper 的动态代理的方式开发 DAO
Mapper 接口开发方法只需要程序员编写 Mapper 接口(相当于 Dao 接口),由 mybatis 框架家根据接口定义创建接口的动态代理对象, 代理对象的方式同上边 Dao 接口实现类方法。这种方式简单的多,但是有一定的规则。具体规则如下:
Mapper.xml 文件中的 namespace 于 mapper 接口的类路径相同。
Mapper 接口方法名和 Mapper.xml 中定义的每个 Statement 的 id 相同。
Mapper 接口方法点的输入参数类型和 Mapper.xml 中定义的每个 sql 的 parameterType 的类型相同。
Mapper 接口方法的输出参数类型和 Mapper.xml 中定义的每个 sql 的 resultType 的类型相同。
有了上面的规则,我们就开始编写 Mapper.xml 映射文件
Mapper.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="com.student.mybatis.mapper.UserMapper">
<select id="findUserById" parameterType="int" resultType="user">
select * from user where uid = #{uid}
</select>
<select id="findUserByName" parameterType="string" resultType="user">
select * from user where name LIKE '%${value}%'
</select>
<delete id="deleteUserById" parameterType="int">
delete from user where uid = #{uid}
</delete>
<insert id="insertUser" parameterType="user">
<selectKey keyProperty="uid" resultType="int" order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
insert into user(name, sex, address) values (#{name}, #{sex}, #{address});
</insert>
<update id="updateUserById" parameterType="user">
update user set name = #{name} where uid = #{id};
</update>
</mapper>
注意:namespace 要改成 UserMapper 这个接口的全路径。
UserMapper.java
public interface UserMapper {
User findUserById(Integer id);
List<User> findUserByName(String name);
void insertUser(User user);
}
切记:需要在 SqlMapConfig.xml 中引入 UserMapper.xml 文件,否则会报错。
<!--批量引入mapper文件 -->
<package name="com.student.mybatis.mapper"/>
测试类 TestMapper.java
public class TestMapper {
private SqlSessionFactory factory;
@Before
public void setUP() throws Exception{
String resources = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resources);
factory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testInsertUser(){
SqlSession session = factory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = new User();
user.setName("熊二");
user.setSex("1");
user.setAddress("小木屋");
mapper.insertUser(user);
session.commit();
}
@Test
public void testFindUserByName(){
SqlSession session = factory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> userList = mapper.findUserByName("二");
for (User user :
userList) {
System.out.println(user);
}
}
@Test
public void testFindUserById(){
SqlSession session = factory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.findUserById(6);
System.out.println(user);
}
}
结果和上面一样,我就不贴图了,贴图占空间太大。
###3. SqlMapConfig.xml
以下是 mybatis 核心配置文件的完整配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 引入数据库的配置文件-->
<properties resource="db.properties"/>
<!-- 配置自定义别名-->
<typeAliases>
<!-- 定义单个别名-->
<!--<typeAlias type="com.student.mybatis.bean.User" alias="user"/>-->
<!-- 批量定义别名-->
<package name="com.student.mybatis.bean"/>
</typeAliases>
<environments default="development">
<environment id="development">
<!-- mybatis 事务管理器-->
<transactionManager type="JDBC"/>
<!-- mybatis 连接池-->
<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>
<!-- 引入单个 SQL 映射文件-->
<!--<mapper resource="com/student/mybatis/bean/User.xml"/>-->
<!-- 引入 单个mapper 文件-->
<!--<mapper class="com.student.mybatis.mapper.UserMapper"/>-->
<!--批量引入mapper文件 -->
<package name="com.student.mybatis.mapper"/>
</mappers>
</configuration>
配置的内容和顺序如下:
- properties(属性)
- settings(全局配置参数)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境集合属性对象)
- environment (环境子属性对象)
- transactionManager (事务管理)
- dataSource (数据源)
- mappers(映射器)
###4. typeAliases(类型别名)
默认支持别名有很多,这个我就不列举了,需要的话直接去文档上找即可。
输入映射和输出映射
Mapper.xml 映射文件中定义了操作数据库的 sql,每个 sql 是一个 statement, 映射文件是 mybatis 的核心。
###1. parameterType(输入类型)
####1.1 传递简单参数
这个就不说了,在上一篇文章中已经详细讲解过了。
####1.2 传递 pojo 类型
mybatis 使用 ognl 表达式解析对象字段的值,#{} 或者 ${} 括号中的值为 pojo 属性名称。
####1.3 传递 pojo 包装对象
开发中通过 pojo 传递查询条件,查询条件是综合的查询条件,不仅包括用户查询条件,还包括其他的查询条件,这个时候可以使用包装对象传递输入参数。
功能: 根据 name 查询用户信息,查询条件放到 QueryVo 中的 user 属性中。
新建类 QueryVo.java
public class QueryVo2 {
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
userMapper.xml 中的映射关系
<select id="findUserNameByQueryVo" parameterType="queryVo" resultType="user">
select * from user where name LIKE '%${user.name}%'
</select>
UserMapper.java 接口中新增方法
List<User> findUserNameByQueryVo(QueryVo2 queryVo2);
测试方法
@Test
public void testFindUserByQueryVo(){
SqlSession session = factory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
QueryVo2 queryVo2 = new QueryVo2();
User user = new User();
user.setName("二");
queryVo2.setUser(user);
List<User> userList = mapper.findUserNameByQueryVo(queryVo2);
for (User u :
userList) {
System.out.println(u);
}
}
###2. resultType(结果类型)
这里我们一般都是在关联查询中才会用到,所以在这里就先不讲了,等下面讲解关联查询的时候,再说输出映射。别猴急猴急的,当心闪了腰!
动态 SQL
mybatis 主要提供了 4 中标签,方便我们动态去拼接 sql 语句。
###1. if
<select id="findUserByQueryVoId" parameterType="queryVo" resultType="user">
select * from user where 1 = 1
<if test="user.sex != null and user.sex != ''">
and sex = #{user.sex};
</if>
</select>
where 后面拼接 1 = 1,是为了让 sql 语句执行不报错。如果不这样做,当 if 里面的条件不满足情况的时候,此时执行的 sql 语句就变成了
select * form user where
很明显,这个 sql 语句有语法错误,所以我们只能在 sql 的后面拼接上 1 = 1, 显然这样的做法并不好,如果需要改善这种情况,需要利用我们的下一个关键字 where 了。
List<User> findUserByQueryVoId(QueryVo2 queryVo2);
测试方法
@Test
public void testFindUserById2(){
SqlSession session = factory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
QueryVo2 queryVo2 = new QueryVo2();
User user = new User();
user.setSex("0");
queryVo2.setUser(user);
List<User> userList = mapper.findUserByQueryVoId(queryVo2);
for (User u : userList) {
System.out.println(u);
}
}
结果:
这里我们和上面一样,着重看一下执行的 sql 语句。
###2. where
为了让上面的 sql 语句执行正确,我们在 sql 语句后面 拼接了一个恒为 true 的 等式,但是有没有办法让我们的 sql 语句既不用拼接 1 = 1 这种没意义的等式,又不用让 if 条件不满足的情况下不报错呢 ?答案是肯定的:有!
把上面的 sql 语句做如下更改,此时在执行看一下 sql 语句:
<select id="findUserByQueryVoId" parameterType="queryVo" resultType="user">
select * from user
<where>
<if test="user.sex != null and user.sex != ''">
and sex = #{user.sex};
</if>
</where>
</select>
结果:(看 sql 语句)
你发现了 if 里面的 and 不见了,没错,where 会自动帮我们处理掉条件的第一个 and。如果我们的条件不满足,where 标签连 where 这个关键字都不会帮我们拼接。现在我再次更改 sql。
<select id="findUserByQueryVoId" parameterType="queryVo" resultType="user">
select * from user
<where>
<if test="user.sex == null and user.sex == ''">
and sex = #{user.sex};
</if>
</where>
</select>
显然 if 里面的条件并不成立。执行结果为:(看 sql 语句)
你会发现连 where 都没有了。这便是 mybatis 动态 sql 的强大之处。
###3. foreach
foreach 顾名思义大致就是循环,比如当我们想执行如下 sql 语句的时候
select * from user where name like '%二%' and uid in(1,3,9,12);
//查询 uid 等于 1,3,9,12 并且 name 中有 “二” 的 User 对象
因为不确定括号中的值,所以这里很明显需要用到循环。初学 mybatis 可能觉得这个 sql 有点复杂,其实不然,只要你能够写出 sql, 那么下面的就不难。
<select id="findUserByForeachQueryVo" parameterType="queryVo" resultType="user">
select * from user
<where>
name like '%${user.name}%'
<foreach collection="ids" item="id" open="and uid in (" separator="," close=")">
#{id}
</foreach>
</where>
</select>
这里就可以看出来我们封装 QueryVo 的好处了,基本上只要我们需要什么,我们都可以在 QueryVo 这个类中去封装。
QueryVo.java
public class QueryVo2 {
private User user;
private List<Integer> ids;
public List<Integer> getIds() {
return ids;
}
public void setIds(List<Integer> ids) {
this.ids = ids;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
因为是要传入一组 id 值,所以我们把 id 封装成一个 List 集合。
collection 这个关键字就是问我们要遍历循环的集合名称,根据 QueryVo,我们知道名称为 ids。
item 这个关键字就是临时变量,就像 for 循环中有一个 i 作为临时变量一样。
open 就是开始拼接的部分,从 sql 语句中我们看出来为 “and uid in (”
separator 显然是分隔符,这里的分隔符就是 “,”
close 所有的都拼接完成了,就剩个结束标签了,从 sql 语句中自然可以看出来是 “)”
测试类在此,谁敢猖狂!
@Test
public void testForeach(){
SqlSession session = factory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
QueryVo2 queryVo2 = new QueryVo2();
List<Integer> list = new ArrayList<>();
User u1 = new User();
u1.setName("二");
queryVo2.setUser(u1);
list.add(1);
list.add(3);
list.add(12);
queryVo2.setIds(list);
List<User> userList = mapper.findUserByForeachQueryVo(queryVo2);
for (User user :
userList) {
System.out.println(user);
}
}
贴出结果,最重要的还是要看控制台输出的 sql 语句。其他的都不重要,主要是 sql 语句是否正确,关注点不要错了。
看 sql 语句
看 sql 语句
看 sql 语句
重要的话说三遍,再不看你就去死吧。
###4. 复用 sql 片段
上面就是 mybatis 实现动态 sql 的三个主要以及最简单的关键字,回过头看 第一个 if 标签中的 sql 片段,如果 if 标签中的判断在很多地方都会用到,这时候我们可以考虑把那个 if 标签抽离出去,然后在需要用到的地方直接引用即可。
<!-- 抽取 sql -->
<sql id="test_if">
<if test="user.sex != null and user.sex != ''">
and sex = #{user.sex};
</if>
</sql>
<!--引用 sql-->
<select id="findUserByQueryVoId" parameterType="queryVo" resultType="user">
select * from user
<where>
<include refid="test_if"/>
</where>
</select>
如果引用其他的 namespace 中的 sql 片段,则需要加上 namespace,比如
<include refId = "namespace.sql"/>
关联查询
这个就厉害了…呃呃呃,不知道怎么描述,反正就是很厉害了。直接说例子吧。
现在数据库中有两张表 user1 和 orders 表
查询出来的结果为
关联两张表查询(还记得我们说的用别名么?如果记得,请自己手写一遍)
//不用别名,查询出 user1 表和 orders 表中的全部列
select * from user1, orders where user1.id = orders.user_id;
//使用别名,查询出 user1 表和 orders 表中的全部列
select * from user1 u, orders o where u.id = o.user_id;
查出来结果很明显有重复的列,我们改正一下 sql 语句, 去除重复的列,以及 orders 中无用的列。
select u.*, o.number, o.createtime, o.note from user1 u, orders o where u.id = o.user_id;
又带你们复习了一下 sql 语句,一定要锻炼手写 sql 的能力。
###1.1 一对一查询
需求:查询所有订单信息,关联查询下单用户信息。
注意:因为一个订单只会属于一个用户,所以从查询订单信息发出关联查询用户信息是一对一查询,如果从用户信息发出查询用户下的订单信息则为一对多查询,因为一个用户可以有多个订单。
简单来说:用户和订单之间的关系就是 一对多的关系。
####1.1.1 方式一(偷懒,简单的方式,但是不是 Mybatis 推荐的查询方式)
使用 resultType,定义订单信息实体类,此类中包括了订单信息和用户信息。
查询所使用的 sql 语句如下
select o.*, u.username, u.address from orders o, user1 u where o.user_id = u.id;
一、 自定义一个新的实体类 OrderAndUser.java
如果是以订单为主(根据订单查用户,是一对一的关联查询),那么新的实体类就应该继承 Order,并把 User.java 中需要查询的属性全部拷贝过来,根据我们上面的 sql 语句来看,我们只需要查询用户表中的 username 和 address 两个字段,故我们只拷贝了两个字段。
如果你想查询 User 表中的全部属性,那就把 User 表中的全部属性全拷贝过来咯。you happy 就 ok,I am 无所谓。
OrderAndUser. java
public class OrderAndUser extends Order{
private String username; //用户表中的username字段
private String address;//用户表中的address字段
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
二、定义 sql 映射关系
<select id="findOrderList" resultType="com.student.mybatis.bean.OrderAndUser">
select o.*, u.username, u.address from orders o, user1 u where o.user_id = u.id;
</select>
三、编写接口方法
List<OrderAndUser> findOrderList();
四、编写测试方法
@Test
public void testFindAllOrders(){
SqlSession session = factory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
List<OrderAndUser> orderList = mapper.findOrderList();
for (OrderAndUser orderAndUser :
orderList) {
System.out.println(orderAndUser);
}
session.close();
}
五、结果演示
友情提示一下,这里只能用 debug 去看,所以我这里也是 debug 模式的截图,看到结果就好了。
上面的这 5 个步骤我这两篇文章写了好多遍了,如果还有不懂的同学,请自行回去看第一篇文章。
####1.1.2 方式二(正规的方式,也是 Mybatis 推荐的方式,这里我同样也推荐你们使用这种)
还记得我们的 sql 标签中有一个 resultMap 标签吗?这个标签就可以定义专门的一对一查询。
一、sql 语句
同上,不写了。有兴趣的自己写一下。
二、定义 POJO 类
在 Order.java 中加入 User 为成员变量。
public class Order {
private Integer id;
private Integer userId;
private String number;
private Date createtime;
private String note;
//添加的 User1对象
private User1 user;
public User1 getUser() {
return user;
}
public void setUser(User1 user) {
this.user = user;
}
//省略了其他的 get set 方法...
}
三、编写 UserMapper.xml 映射文件
<!-- 查询订单关联的用户信息 使用resultMap-->
<!-- <!-- type 是主表 order 对应的实体类 order 的别名-->-->
<resultMap id="orderUserResultMap" type="order">
<!-- column 是数据库中的字段名, property 是实体对象的属性名称-->
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<!--使用 association 进行关联查询,将查询的结果映射到 User1 对象中,只关联查询了username 和 address 两个属性,就只写这两个属性 -->
<association property="user" javaType="com.student.mybatis.bean.User1">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="address" property="address"/>
</association>
</resultMap>
<select id="findOrdersWithUserResultMap" resultMap="orderUserResultMap">
select o.id , o.user_id, o.number, o.createtime, o.note, u.username, u.address from orders o join user1 u on u.id = o.user_id;
</select>
assocition: 表示关联查询的单条记录。
property: 表示关联查询的结果集存储在 Order对象中的 user 属性中。
javaType: 表示关联查询的结果集类型,这里可以使用别名。
四、编写测试方法
@Test
public void testResultMap1(){
SqlSession session = factory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
List<Order> orderList = mapper.findOrdersWithUserResultMap();
for (Order order :
orderList) {
System.out.println(order);
}
session.close();
}
五、查询结果
内容有点不够展示,所以我就只截图了前两个,不过前两个已经够说明问题了。
###1.2 一对多查询
功能:查询所有用户信息以及用户对应的订单。明显是一个多对一的问题。同样使用 resultMap 来完成。
一、编写 sql
select u.*, o.id, o.number, o.createtime, o.note from user1 u left join orders o on u.id = o.user_id;
二、定义 POJO 类
public class User1 {
private Integer id;
private String username;
private String address;
private Date birthday;
private String sex;
//一个用户对应多个订单,多以这里用的是 List 集合
private List<Order> orderList;
public List<Order> getOrderList() {
return orderList;
}
public void setOrderList(List<Order> orderList) {
this.orderList = orderList;
}
}
三、编写 sql 映射
<!-- 以用户为主查询用户关联下的订单信息-->
<!-- type 是主表 user1 对应的实体类 User1 的别名-->
<resultMap id="userOrderResultMap" type="user1">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="address" property="address"/>
<result column="birthday" property="birthday"/>
<result column="sex" property="sex"/>
<!-- 由于是关联集合,所以这里用的是 collection -->
<!-- offType 是从表对应的实体类 Order 的别名-->
<collection property="orderList" ofType="order">
<id column="id" property="id"/>
<result column="user_id" property="userId"/><!-- 这行可以不写,因为从 user 表中我们已经查出了 id,重复展示出来没有什么意义-->
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
</collection>
</resultMap>
<!-- 这里的 sql 语句同样用到了 连接查询 -->
<select id="findUsersWithOrderResultMap" resultMap="userOrderResultMap">
select u.*, o.id, o.number, o.createtime, o.note from user1 u left join orders o on u.id = o.user_id;
</select>
property=“orderList” : 关联查询的结果存储在 User1 对象中的哪一个属性上,
ofType=“order” :指定关联查询的结果中的对象类型,即 List 中的对象类型,这里可以使用别名,同样也可以使用全限定名。
四、编写测试方法
@Test
public void testResultMap2(){
SqlSession session = factory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
List<User1> user1List = mapper.findUsersWithOrderResultMap();
for (User1 user :
user1List) {
System.out.println(user);
}
}
五、执行结果
结果太多,我就不展开看了,具体的情况你们自己看截图就可以了。大部分人都应该是看的明白的。
结尾
这篇文章写到这里,关于 mybatis 的大部分知识都已经完结了。我翻了一下,这篇文章好像有点长。不过不影响阅读,这篇文章的内容是由浅入深的,所以我觉得理解起来也不是很困难。好啦,写到这里,也该结束了,太长了看起来也累。本人能力有限,所以只能写出这样的文章,写作过程中,难免会出现错误,如果有错误还请各位能够指出,当然也欢迎志同道合的小伙伴们留言交流。