学习笔记--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 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 findByCondition(User user);

然后在Mapper.xml中配置sql语句


标签套在if标签族外,作用与sql语句中where语句相同,当里面if有填充时,起where作用,里面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 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语句。


collection后面跟的是数据类型,一般是集合或者数组,open是以什么开头,close是以什么结尾,item是每一项的名称,separator是中间间隔。

上面的拼接最后 select * from user WHERE id in( ? , ? )

集合中有多少个数字,就会有几个问号。

2.2 sql语句的抽取

当我们写sql语句的时候,我们会重复写很多相同的sql语句,比如上述例子中的

select * from user

我们可以用sql标签来抽取这个重复的sql语句,来达到解耦合的作用。


    
        select * from user
    

使用sql标签:


    

3.MyBatis核心配置文件深入

3.1 typeHandlers标签

将java数据和数据库数据进行转换

做法:实现org.apache.ibatis.type.TypeHandler接口 或继承一个很便利的类 org.apache.type.BaseTypeHandler

然后可以选择性地将它映射到一个JDBC类型。

开发步骤:

1、定义转换类继承类BaseTypeHandler

public class DateTypeHandler extends BaseTypeHandler {
}

2、覆盖4个未实现的方法,其中setNonNullParameter为java程序设置数据到数据库的回调方法(java->sql),getNullableResult为查询时mysql的字符串类型转换成java的Type类型的方法(sql->java)。

public class DateTypeHandler extends BaseTypeHandler {

    //将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核心配置文件中进行注册

  
        
    

4、测试转换是否正确

3.2 plugins标签---PageHepler(分页)

MyBatis可以使用第三方插件来对功能进行扩展,分页助手PageHelper是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据。

开发步骤:

1、导入通用PageHepler的坐标

        
            com.github.pagehelper
            pagehelper
            3.7.5
        

        
            com.github.jsqlparser
            jsqlparser
            1.4
                

2、在mybatis核心配置文件中配置PageHelper插件


        
            
        
    

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 userList = mapper.findAll();
        for (User user : userList) {
            System.out.println(user);
        }

        PageInfo pageInfo = new PageInfo(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语句:

 

因为两张表都有id字段,所以将orders表中的id起了个别名oid。

查询结果:

如果通过mybatis自动匹配机制,查询到的结果不能直接被封装到订单对象(Order)中的用户对象(User)里,所以要手动配置映射关系。

方法一:


        
        
        
        
        
        
        
        

方法二:


        
        
        
        





        
        
            
            
            
            
        

4.2 一对多查询

本节要完成的一对多查询是根据用户,查询该用户所有的订单。

对应的sql语句:

select *,o.id oid from user u,orders o where u.id=o.uid

同样需要手动指定字段和实体属性的映射关系:


        
        
        
        
        
        
            
            
            
        
    

    

需要注意 该映射关系,如果遇见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

借助中间表来完成


        
        
        
        

        
            
            
            
        
    
    
    

与一对多类似。

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 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核心配置类中设置映射关系


    
        
        
    

好处:简化了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 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 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 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 findUserAndRoleAll();

首先查user表,查到的数据再以user中的id为参数,去查sys_user_role表和sys_role表,先将中间的表的roleid和角色表的id对应,然后再限定userId,将得到的角色全部封装进list中。