学习笔记--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自动生成动态代理对象帮我们去实现这个接口类
1 2 3 4 5 6 | public interface UserMapper { public List<User> findAll(); public User findById( int id); } |
1 2 3 4 5 6 | 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标签来组成动态语句。
按照惯例先写接口类的接口方法
1 | public List<User> findByCondition(User user); |
然后在Mapper.xml中配置sql语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | < 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语句,如果不满足,则不会组成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @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语句。
1 2 3 4 5 6 7 8 | <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语句,比如上述例子中的
1 | select * from user |
我们可以用sql标签来抽取这个重复的sql语句,来达到解耦合的作用。
1 2 3 4 | <!-- sql语句的抽取 --> < sql id="selectUser"> select * from user </ sql > |
使用sql标签:
1 2 3 4 | <!-- 根据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>
1 2 | public class DateTypeHandler extends BaseTypeHandler<Date> { } |
2、覆盖4个未实现的方法,其中setNonNullParameter为java程序设置数据到数据库的回调方法(java->sql),getNullableResult为查询时mysql的字符串类型转换成java的Type类型的方法(sql->java)。
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 | 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核心配置文件中进行注册
1 2 3 | <typeHandlers> <typeHandler handler= "com.xc.handler.DateTypeHandler" /> </typeHandlers> |
4、测试转换是否正确
3.2 plugins标签---PageHepler(分页)
MyBatis可以使用第三方插件来对功能进行扩展,分页助手PageHelper是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据。
开发步骤:
1、导入通用PageHepler的坐标
1 2 3 4 5 6 7 8 9 10 11 | <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插件
1 2 3 4 5 | <plugins> <plugin interceptor= "com.github.pagehelper.PageHelper" > <property name= "dialect" value= "mysql" /> </plugin> </plugins> |
dialect可以理解成“方言”,不同的数据库技术对应不同的值。
3、测试分页数据获取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | @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语句:
1 2 3 | < 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)里,所以要手动配置映射关系。
方法一:
1 2 3 4 5 6 7 8 9 10 11 12 13 | < 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 > |
方法二:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | < 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语句:
1 | select *,o.id oid from user u,orders o where u.id=o.uid |
同样需要手动指定字段和实体属性的映射关系:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | < 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语句:
1 | select * from user u,sys_user_role ur,sys_role r where u.id=ur.userId AND ur.roleId=r.id |
借助中间表来完成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | < 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语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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核心配置类中设置映射关系
1 2 3 4 5 | <!-- 加载映射关系 --> < mappers > <!-- 指定接口的包--> < package name="com.xc.dao"/> </ mappers > |
好处:简化了mapper文件的开发,避免对应关系可以极大的加快编写速度和节省精力。
5.3 复杂查询
5.3.1 一对一注解查询
方法一:
1 2 3 4 5 6 7 8 9 10 | @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中可以写一一映射关系,值和上述差不多。
方法二:
1 2 3 4 5 6 7 8 9 10 11 12 13 | @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次表查询
1 2 3 4 5 6 7 8 9 10 11 12 13 | @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次表查询
1 2 3 4 5 6 7 8 9 10 11 12 13 | @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中。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器
· 面试官:你是如何进行SQL调优的?