学习笔记--Mybatis(二)
1.MyBatis的Dao层实现
1.1 传统开发方式(需要写接口实现)
配置完核心配置文件和映射文件之后
Dao层创建一个接口类,接口有和数据库之间的交互方法。
然后创建一个实现类,实现类获得配置文件、工厂对象、会话对象之后调用会话方法从数据库获取数据,然后返回给Service层。
Service层拿到数据后实现业务方法。
1.2 代理开发方式(不需要写接口实现)
1 代理开发方式介绍
采用Mybatis的代理开发方式实现DAO层的开发,是主流。
Mapper接口开发方式只需要程序员编写Mapper接口(相当于Dao接口),由Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法同上边Dao接口实现类方法。
Mapper接口开发需要遵循以下规范:
1、Mapper.xml文件中的namespace与mapper接口的全限定名相同。
2、Mapper接口方法名和Mapper.xml中定义的每个statement的id相同。
3、Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同。
4、Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同。、
如图就是规范相同的内容。

这样的好处就是只需要编写Mapper接口,不需要写实现类,由Mybatis自动生成动态代理对象帮我们去实现这个接口类
public interface UserMapper {
public List<User> findAll();
public User findById(int id);
}
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.findById(1);
System.out.println(user);
通过sqlSession.getMapper获取mapper对象。
这个mapper相当于是获取的实现类,直接调用实现类的方法即可。
2.MyBatis映射文件深入
2.1 动态sql语句
之前的学的sql都是比较简单的,但有些时候业务逻辑复杂时,我们的SQL是动态变化的,此时我们之前学的就无法满足我们的要求。
2.1.1 if 标签
我们会遇到这种情况,在不同场景下,根据不同条件搜索数据库,所以不能用固定的sql语句来搜索数据库,这时候就要用到where+if标签来组成动态语句。
按照惯例先写接口类的接口方法
public List<User> findByCondition(User user);
然后在Mapper.xml中配置sql语句
<select id="findByCondition" parameterType="user" resultType="user">
select * from user
<where>
<if test="id!=0">
and id = #{id}
</if>
<if test="username!=null">
and username = #{username}
</if>
<if test="password!=null">
and password = #{password}
</if>
</where>
</select>
<where>标签套在if标签族外,作用与sql语句中where语句相同,当里面if有填充时,起where作用,里面if没填充时,就不起作用。
<if> 标签 当满足if标签里面的条件时,则把其中的语句组成sql语句,如果不满足,则不会组成。
@Test
public void test1() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User condition = new User();
condition.setId(1);
// condition.setUsername("zhangsan");
condition.setPassword("123");
List<User> userList = mapper.findByCondition(condition);
System.out.println(userList);
}
当把条件中的“zhangsan”注掉时,相当于执行sql语句:
select * from user WHERE id = ? and password = ?
当把条件中的“id”注掉时,相当于执行sql语句:
select * from user WHERE username = ? and password = ?
类似,如果全部注解,则执行:
select * from user
2.1.2 foreach标签
foreach标签主要作用是根据集合里的数据循环入sql语句。
<select id="findByIds" parameterType="list" resultType="user">
select * from user
<where>
<foreach collection="list" open="id in(" close=")" item="id" separator=",">
#{id}
</foreach>
</where>
</select>
collection后面跟的是数据类型,一般是集合或者数组,open是以什么开头,close是以什么结尾,item是每一项的名称,separator是中间间隔。
上面的拼接最后 select * from user WHERE id in( ? , ? )
集合中有多少个数字,就会有几个问号。
2.2 sql语句的抽取
当我们写sql语句的时候,我们会重复写很多相同的sql语句,比如上述例子中的
select * from user
我们可以用sql标签来抽取这个重复的sql语句,来达到解耦合的作用。
<!-- sql语句的抽取 -->
<sql id="selectUser">
select * from user
</sql>
使用sql标签:
<!-- 根据id查询-->
<select id="findById" parameterType="int" resultType="user">
<include refid="selectUser"/> where id=#{id}
</select>
3.MyBatis核心配置文件深入
3.1 typeHandlers标签
将java数据和数据库数据进行转换
做法:实现org.apache.ibatis.type.TypeHandler接口 或继承一个很便利的类 org.apache.type.BaseTypeHandler
然后可以选择性地将它映射到一个JDBC类型。
开发步骤:
1、定义转换类继承类BaseTypeHandler<T>
public class DateTypeHandler extends BaseTypeHandler<Date> {
}
2、覆盖4个未实现的方法,其中setNonNullParameter为java程序设置数据到数据库的回调方法(java->sql),getNullableResult为查询时mysql的字符串类型转换成java的Type类型的方法(sql->java)。
public class DateTypeHandler extends BaseTypeHandler<Date> {
//将java类型的数据转换成数据库需要的类型
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Date date, JdbcType jdbcType) throws SQLException {
//date.getTime返回当前时间的毫秒值
long time = date.getTime();
//i是参数所在位置,意思是将long形式的数据作为参数填入数据库
preparedStatement.setLong(i,time);
}
//将数据库的类型转换成java需要的类型
// String 参数 要转换的字段名称
// ResultSet 查询出的结果集
public Date getNullableResult(ResultSet resultSet, String s) throws SQLException {
//获得结果集中需要的数据(long)转换成Date类型
long aLong = resultSet.getLong(s);
Date date = new Date(aLong);
return date;
}
public Date getNullableResult(ResultSet resultSet, int i) throws SQLException {
//获得结果集中需要的数据(long)转换成Date类型
long aLong = resultSet.getLong(i);
Date date = new Date(aLong);
return date;
}
public Date getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
long aLong = callableStatement.getLong(i);
Date date = new Date(aLong);
return date;
}
}
以后再次碰见Date类型的数据,就会自动使用类型转换器来帮助我们实现数据库和java数据类型之间的转换。
3、在MyBatis核心配置文件中进行注册
<typeHandlers>
<typeHandler handler="com.xc.handler.DateTypeHandler"/>
</typeHandlers>
4、测试转换是否正确
3.2 plugins标签---PageHepler(分页)
MyBatis可以使用第三方插件来对功能进行扩展,分页助手PageHelper是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据。
开发步骤:
1、导入通用PageHepler的坐标
<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>1.4</version>
</dependency>
2、在mybatis核心配置文件中配置PageHelper插件
<plugins>
<plugin interceptor="com.github.pagehelper.PageHelper">
<property name="dialect" value="mysql"/>
</plugin>
</plugins>
dialect可以理解成“方言”,不同的数据库技术对应不同的值。
3、测试分页数据获取
@Test
public void test5() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//设置分页相关参数 当前页+每页显示的参数
PageHelper.startPage(1,3);
List<User> userList = mapper.findAll();
for (User user : userList) {
System.out.println(user);
}
PageInfo<User> pageInfo = new PageInfo<User>(userList);
System.out.println("当前页:"+ pageInfo.getPageNum());
System.out.println("每页显示条数:"+ pageInfo.getPageSize());
System.out.println("总条数:" + pageInfo.getTotal());
System.out.println("总页数:" + pageInfo.getPages());
sqlSession.close();
}
pageInfo类也封装了许多关于分页的信息,可以直接调用其中的方法来获取分页的信息。
4.MyBatis的多表操作
4.1 一对一查询
本节要完成的是通过订单查询,同时查询到该订单对应的User表的信息
sql语句:
<select id="findAll" resultMap="orderMap">
select *,o.id oid from orders o,user u where o.uid=u.id
</select>
因为两张表都有id字段,所以将orders表中的id起了个别名oid。
查询结果:

如果通过mybatis自动匹配机制,查询到的结果不能直接被封装到订单对象(Order)中的用户对象(User)里,所以要手动配置映射关系。
方法一:
<resultMap id="orderMap" type="order">
<!-- 手动指定字段与实体属性的映射关系
column:数据表的字段名称
propery:实体的属性名称
-->
<id column="oid" property="id"/>
<result column="odertime" 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>
方法二:
<resultMap id="orderMap" type="order">
<!-- 手动指定字段与实体属性的映射关系
column:数据表的字段名称
propery:实体的属性名称
-->
<id column="oid" property="id"/>
<result column="odertime" 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"/>-->
<!-- 这里的property的含义是order类中的user属性
javaType的含义是该user属性的类型,照理来说应该是com.xc.domain.Order
因为起了别名,所以内容和前者相同,但含义不同
-->
<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>
</resultMap>
4.2 一对多查询
本节要完成的一对多查询是根据用户,查询该用户所有的订单。
对应的sql语句:
select *,o.id oid from user u,orders o where u.id=o.uid
同样需要手动指定字段和实体属性的映射关系:
<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">
<id column="oid" property="id"/>
<result column="ordertime" property="ordertime" jdbcType="DATE"/>
<result column="total" property="total"/>
</collection>
</resultMap>
<select id="findAllOrders" resultMap="userMap">
select *,o.id oid from user u,orders o where u.id=o.uid
</select>
需要注意 该映射关系,如果遇见sql中的date或datetime属性,需要额外设置其jdbcType的值。
遇到date 设其为“DATE”,
遇到datetime 设其为"TIMESTAMP"
id是主键,非常重要,这样的话mybatis会根据你的主键生成对象,一个主键即一个对象。
4.3 多对多查询
本节要完成的查询是,多个用户拥有多个角色信息。
sql语句:
select * from user u,sys_user_role ur,sys_role r where u.id=ur.userId AND ur.roleId=r.id
借助中间表来完成
<resultMap id="userRoleMap" type="user">
<id column="userId" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="birthday" property="birthday"/>
<collection property="roleList" ofType="role">
<id column="roleId" property="id"/>
<result column="roleName" property="roleName"/>
<result column="roleDesc" property="roleDesc"/>
</collection>
</resultMap>
<select id="findUserAndRole" resultMap="userRoleMap">
select * from user u,sys_user_role ur,sys_role r where u.id=ur.userId AND ur.roleId=r.id
</select>
与一对多类似。
5 MyBatis的注解开发
5.1 常用注解
MyBatis也可以使用注解开发方式,减少Mapper映射文件的编写。
@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result一起使用,封装多个结果集
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
5.2 简单查询
步骤:
1、写接口映射
2、在接口方法上面利用注解写如sql语句
public interface UserMapper {
@Select("select * from user")
public List<User> findAll();
@Select("select * from user where id=#{id}")
public User findById(int id);
@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);
}
3、在mybatis核心配置类中设置映射关系
<!-- 加载映射关系 -->
<mappers>
<!-- 指定接口的包-->
<package name="com.xc.dao"/>
</mappers>
好处:简化了mapper文件的开发,避免对应关系可以极大的加快编写速度和节省精力。
5.3 复杂查询
5.3.1 一对一注解查询
方法一:
@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",jdbcType= JdbcType.DATE),
@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();
直接查询多表,然后将映射关系写进Results里,result中可以写一一映射关系,值和上述差不多。
方法二:
@Select("select * from orders")
@Results({
@Result(column = "oid",property = "id"),
@Result(column = "ordertime",property = "ordertime",jdbcType= JdbcType.DATE),
@Result(column = "total",property = "total"),
@Result(
property = "user",//封装的属性名称
column = "uid",//根据哪个字段去查询user表的数据
javaType = User.class,//要封装的实体类型
one = @One(select = "com.xc.dao.UserMapper.findById")
)
})
public List<Order> findAll();
分2次表查询,首先查订单表,然后根据订单表中用户的id字段,再查用户表,将查到的用户信息封装进对应订单的对象中。
5.3.2 一对多查询
一对多同样是分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.xc.dao.OrderMapper.findByUid")
)
})
public List<User> findUserAndOrderAll();
首先查user表,查到的数据再以user中的id为参数,去查order表,将该id下的所有订单以一对多(@Many)的方式封装进集合中
5.3.3 多对多查询
多对多同样是分2次表查询
@Select("SELECT * FROM user")
@Results({
@Result(id=true,column ="id",property = "id"),
@Result(column ="username",property = "username"),
@Result(column ="password",property = "password"),
@Result(
column = "id",
property = "roleList",
javaType = List.class,
many = @Many(select = "com.xc.dao.RoleMapper.findByUid")
)
})
public List<User> findUserAndRoleAll();
首先查user表,查到的数据再以user中的id为参数,去查sys_user_role表和sys_role表,先将中间的表的roleid和角色表的id对应,然后再限定userId,将得到的角色全部封装进list中。
浙公网安备 33010602011771号