资讯专栏INFORMATION COLUMN

MyBatis学习笔记(二)——DAO的开发、配置文件、动态SQL

plus2047 / 2240人阅读

摘要:建议不要在元素体内添加任何属性值,只将属性值定义在文件中。在文件中定义属性名要有一定的特殊性防止覆盖,如配置全局参数,在运行时可以调整一些运行参数。同样需要遵守上面的规范。动态核心对语句进行灵活操作,通过表达式进行判断,对进行灵活拼接组装。

Hibernate与Mybatis的本质区别和应用场景

Hibernate:标准的ORM框架,不需要写SQL语句,但是优化和修改SQL语句比较难。

应用于需求变化固定的中小型的项目,例如后台管理系统、ERP、ORM、OA。

Mybatis:专注SQL本身,SQL的优化比较方便,是不完全的ORM。

主要适用于需求变化较多的项目,例如互联网项目。

mybatis开发dao的方法 SqlSession的应用场合 SqlSessionFactoryBuilder

通过SqlSessionFactoryBuilder创建会话工厂SqlSessionFactory
将SqlSessionFactoryBuilder当成一个工具类使用即可,不需要使用单例管理SqlSessionFactoryBuilder。
在需要创建SqlSessionFactory时候,只需要new一次SqlSessionFactoryBuilder即可。

SqlSessionFactory

通过SqlSessionFactory创建SqlSession,使用单例模式管理sqlSessionFactory(工厂一旦创建,使用一个实例)。
mybatis和spring整合后,可以使用Ioc容器管理。

SqlSession

SqlSession是一个面向用户(程序员)的接口。
SqlSession中提供了很多操作数据库的方法:如:selectOne(返回单个对象)、selectList(返回单个或多个对象)。
SqlSession是线程不安全的,在SqlSesion实现类中除了有接口中的方法(操作数据库的方法)还有数据域属性。
SqlSession最佳应用场合在方法体内,定义成局部变量使用。

原始的DAO开发方法

程序员需要编写DAO和DAO的实现类。需要向DAO实现类中注入SqlSessionFactory,在方法体内通过SqlSessionFactory来创建SqlSession。

定义DAO接口

public interface UserDAO {

    /**
     * 根据本id查询用户
     */
    User findUserById(int id) throws Exception;
    
    /**
     * 添加用户
     */
    void insertUser(User user) throws Exception;
    
    /**
     * 根据id删除用户
     */
    void deleteUser(int id) throws Exception;
}

DAO实现类

/**
* @ClassName: UserDAOImpl 
* @Description: DAO实现类(注意:SqlSession是非线程安全的,故不能声明为全局的)
 */
public class UserDAOImpl implements UserDAO {

    SqlSessionFactory sqlSessionFactory;
    /**
     * 向DAO实现类中注入SqlSessionFactory(此处通过构造方法注入)
     */
    public UserDAOImpl(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }
    
    @Override
    public User findUserById(int id) throws Exception {
        
        SqlSession sqlSession = sqlSessionFactory.openSession();
        User user = sqlSession.selectOne("test.findUserById", id);
        sqlSession.close();
        return user;
    }

    @Override
    public void insertUser(User user) throws Exception {
        
        SqlSession sqlSession = sqlSessionFactory.openSession();
        sqlSession.insert("test.insertUser", user);
        sqlSession.commit();
        sqlSession.close();
    }

    @Override
    public void deleteUser(int id) throws Exception {
        
        SqlSession sqlSession = sqlSessionFactory.openSession();
        sqlSession.delete("test.deleteUser", id);
        sqlSession.commit();
        sqlSession.close();
    }

}

新建一个源代码目录命名为test,在UserDAOImpl类中鼠标右键新建一个Junit Test Case,更换源代码目录为test并勾选我们需要测试的方法:

public class UserDAOImplTest {

    private SqlSessionFactory sqlSessionFactory;
    
    /**
     * 此方法在执行测试之前执行,得到一个SqlSessionFactory
     */
    @Before
    public void setUp() throws Exception {
        
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("SqlMapConfig.xml"));
    }

    @Test
    public void testFindUserById() throws Exception {
        // 创建UserDAO(在构造中注入SqlSessionFactory)
        UserDAO userDAO = new UserDAOImpl(sqlSessionFactory);
        User user = userDAO.findUserById(1);
        System.out.println(user);
    }

    @Test
    public void testInsertUser() throws Exception {
        UserDAO userDAO = new UserDAOImpl(sqlSessionFactory);
        User user = new User();
        user.setSex("2");
        user.setUsername("孙悟空");
        user.setAddress("方寸灵台山");
        user.setBirthday(new Date());
        userDAO.insertUser(user);
    }

    @Test
    public void testDeleteUser() throws Exception {
        UserDAO userDAO = new UserDAOImpl(sqlSessionFactory);
        userDAO.deleteUser(27);
    }

}
原始DAO开发中存在的问题

DAO的接口实现类中存在大量的模板方法,设想:可以将重复的代码提取出来。

在SqlSession的方法时将Statement的id硬编码在你DAO的实现类中。

调用sqlSession的相关方法传入参数是泛型,即使传入错误的参数,编译时候也不会报错

Mapper接口开发

程序员只需要编写Mapper接口(相当于DAO接口)和Mapper.xml。Mapper接口的编写需要遵守相关规范:

mapper.xml中的命名空间等于Mapper接口类的全路径;

mapper.java中的方法名和mapper.xml中的statement的id一致;

mapper接口中方法的输入参数类型和mapper.xml中的ParameterType一致;

mapper接口中的方法的返回值类型和mapper.xml中的ResultType一致。

遵循以上规范,mybatis就可以自动生成(反射机制)相关的mapper代理的实现类的对象。

UserMapper.xml




    










    
         SELECT  LAST_INSERT_ID();
    
    INSERT INTO user(username,birthday,sex,address) VALUES(#{username},#{birthday},#{sex},#{address});



    DELETE FROM user WHERE id = #{id}



    UPDATE user SET username = #{username},birthday=#{birthday},sex=#{sex},address=#{address} WHERE id = #{id}

UserMapper.java

/**
* @Description: 用户管理相关Mapper接口
 */
public interface UserMapper {

    /**
     * 根据本id查询用户
     */
    User findUserById(int id) throws Exception;
    
    /**
     * 根据用户名模糊查询用户
     */
    List findUsersByName(String name) throws Exception;
    
    /**
     * 添加用户
     */
    void insertUser(User user) throws Exception;
    
    /**
     * 根据id删除用户
     */
    void deleteUser(int id) throws Exception;
}

完成前面2步之后不要忘了在映射文件SqlMapConfig.xml中加载UserMapper.xml哦!



    
    

针对UserMapper接口编写Junit单元测试:

public class UserMapperTest {

    private SqlSessionFactory sqlSessionFactory;
    
    @Before
    public void setUp() throws Exception {
        
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("SqlMapConfig.xml"));
    }

    @Test
    public void testFindUserById() throws Exception {
        
        // 得到SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 得到UserMapper对象(Mybatis自动生成Mapper代理对象)
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        // 调用UserMapper的方法
        User user = userMapper.findUserById(1);
        System.out.println(user);
        // 关闭SqlSession
        sqlSession.close();
    }

    @Test
    public void testFindUsersByName() throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List users = userMapper.findUsersByName("明");
        System.out.println(users);
        sqlSession.close();
    }

    @Test
    public void testInsertUser() throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = new User();
        user.setAddress("晴川");
        user.setUsername("百里屠苏");
        user.setBirthday(new Date());
        user.setSex("1");
        userMapper.insertUser(user);
        sqlSession.commit();
        sqlSession.close();
        System.out.println(user.getId());
    }

    @Test
    public void testDeleteUser() throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        userMapper.deleteUser(28);
        sqlSession.commit();
        sqlSession.close();
    }

}

我们比较疑问的就是我们在UserMapper.xml的根据用户名查找用户的ResultType使用的是普通的POJO,但是我们自己的Mapper接口中返回值是List类型。这不就造成了类型不一致么?但是,实际上代理对象内部调用了selectOne()或者selectList(),代理对象内部会自动进行判断是否是多带带的POJO选用合适的方法。

Mapper接口中方法的参数只有一个是否会影响系统的维护?DAO层的代码是被业务层公用的,即使Mapper接口的参数只有一个我们也可以使用包装的POJO来满足系统需求。

注意:持久层中方法的参数中可以使用包装类型,但是Service层中不建议使用包装类型(不利于业务层的拓展维护)。

SqlMapConfig.xml文件 属性配置

该配置文件是mybatis的全局配置文件,配置内容如下:

properties(属性)
settings(全局配置参数)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境集合属性对象)
environment(环境子属性对象)
transactionManager(事务管理)
dataSource(数据源)
mappers(映射器)

将数据库连接参数多带带配置在db.properties中,只需要在SqlMapConfig.xml中加载db.properties的属性值。
在SqlMapConfig.xml中就不需要对数据库连接参数硬编码。

将数据库连接参数只配置在db.properties中,原因:方便对参数进行统一管理,其它xml可以引用该db.properties。

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=mysqladmin

在SqlMapConfig中加载属性文件:





    
    
        
    
    
    
        
            
            
                
            
             
             
                
                
                
                
            
        
    

properties特性:

注意: MyBatis 将按照下面的顺序来加载属性:

在 properties 元素体内定义的属性首先被读取。

然后会读取properties 元素中resource或 url 加载的属性,它会覆盖已读取的同名属性。

最后读取parameterType传递的属性,它会覆盖已读取的同名属性。

建议:
不要在properties元素体内添加任何属性值,只将属性值定义在properties文件中。在properties文件中定义属性名要有一定的特殊性(防止覆盖),如:XXXXX.XXXXX.XXXX

 settings

settings配置全局参数,mybatis在运行时可以调整一些运行参数。例如:开启二级缓存、开启延迟加载。参数详见:Mybatis settings详解

typeAliases

在mapper.xml中,定义很多的statement,statement需要parameterType指定输入参数的类型、需要resultType指定输出结果的映射类型。

如果在指定类型时输入类型全路径,不方便进行开发,可以针对parameterType或resultType指定的类型定义一些别名,在mapper.xml中通过别名定义,方便开发。

mybatis内置的别名

自定义别名

在SqlMapConfig.xml中配置别名:



    
    

在mapperr.xml中使用别名替代ResultType:


以上的方法是定义简单的单个别名,我们也可以使用批量别名定义:



    
    

在mapper.xml中我们可以使用以上的别名,此时是大小写不敏感的:


其中批量设置别名比较常用(只需要配置po所在的类路径即可)。

TypeHandlers

类型处理器,mybatis通过typeHandlers完成JDBC类型和java类型的转化。一般来说自带的类型处理器已经够用了,不需要多带带定义。

mapper

映射配置。

通过resource一次加载一个映射文件:

通过mapper接口加载单个映射文件,需要遵循相关的规范:将mapper.xml和mapper接口的名称保持一致且在一个目录中(前提是使用mapper代理的方式进行开发)。


批量加载mapper。同样需要遵守上面的规范。

输入映射

通过parameterType来指定输入参数的类型,可以是简单类型、hashmap、pojo。

传递POJO的包装对象

需求:完成用户信息的综合查询,需要传入查询条件很复杂(可能包括用户信息、其它信息,比如商品、订单的)

针对上边需求,建议使用自定义的包装类型的pojo。在包装类型的pojo中将复杂的查询条件包装进去。

新建一个UserCustomer类(该类继承User,实现对Us而的拓展)

/**
* @ClassName: UserCustom 
* @Description: 用户拓展类
 */
public class UserCustom extends User {

}

新建一个UserQueryVO(在该类内部以组合的方式包装UserCustomer类)

/**
* @ClassName: UserQueryVO 
* @Description: 视图层面javabean(用来包装查询条件)
 */
public class UserQueryVO {

    // 包装所需要的查询条件(可以通过组合的方式包装其他的查询条件,例如:商品、订单)
    private UserCustom userCustom;

    public UserCustom getUserCustom() {
        return userCustom;
    }

    public void setUserCustom(UserCustom userCustom) {
        this.userCustom = userCustom;
    }
    
}

mapper.xml。在UserMapper.xml中定义用户信息的综合(复杂的关联查询)查询。


mapper.java。在UserMapper接口中增加一个方法。

/**
 * 用户信息的综合查询
 */
List findUserList(UserQueryVO userQueryVO) throws Exception;

测试类

@Test
public void testfindUserList() throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    
    // 创建包装对象,传入查询条件
    UserQueryVO userQueryVO = new UserQueryVO();
    UserCustom userCustom = new UserCustom();
    userCustom.setSex("1");
    userCustom.setUsername("明");
    userQueryVO.setUserCustom(userCustom);
    List users = userMapper.findUserList(userQueryVO);
    System.out.println(users);
}
输出映射 ResultType

使用ResultType进行输出映射只有当查询输出列名和POJO中的字段名一致时,该列才可以映射成功;如果查询出来的列名和属性名全部不一致(例如使用了SQL的别名);一旦有一个查询的输出列名和POJO的属性名一致就会映射成对象。

需求:用户信息的综合查询列表总数,并结合上边的综合查询列表实现分页功能。

mapper.xml


mapper.java

/**
 * 用户信息的综合查询总数
 */
int findUserCount(UserQueryVO userQueryVO) throws Exception;

测试类:

@Test
public void testFindUserCount() throws Exception{
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    
    // 创建包装对象,传入查询条件
    UserQueryVO userQueryVO = new UserQueryVO();
    UserCustom userCustom = new UserCustom();
    userCustom.setSex("1");
    userCustom.setUsername("明");
    userQueryVO.setUserCustom(userCustom);
    int count = userMapper.findUserCount(userQueryVO);
    System.out.println(count);
}

小结:

查询出来的结果集只有一行且一列,可以使用简单类型进行输出映射。

输出POJO对象和POJO列表。

不管是输出的pojo单个对象还是一个列表(list中包括pojo),在mapper.xml中resultType指定的类型是一样的。
在mapper.java指定的方法返回值类型不一样

生成的动态代理对象中是根据mapper方法的返回值类型确定是调用selectOne(返回单个对象调用)还是selectList (返回集合对象调用 ).

ResultMap

可以使用ResultMap来完成高级的输出结果映射。如果数据库输出的列名和POJO的属性名不一致,就可以通过定义ResultMap对列名和属性名进行我们自己的映射。

范例:使用ResultMap改写以下的SQL,完成输出结果的映射;

SELECT id _id,username _username FROM user WHERE id = #{value}

UserMapper.xml



    
    
    
    


UserMapper.java

/**
 * 根据id查询用户(通过ResultMap手工完成映射)
 */
User findUserByIdResultMap(int id) throws Exception;

测试类:

@Test
public void testFindUserByIdResultMap() throws Exception{
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    User user = userMapper.findUserByIdResultMap(1);
    System.out.println(user);
    sqlSession.close();
}

小结:

使用resultType进行输出映射,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功。

如果查询出来的列名和pojo的属性名不一致,通过定义一个resultMap对列名和pojo属性名之间作一个映射关系。

动态SQL

mybatis核心对sql语句进行灵活操作,通过表达式进行判断,对sql进行灵活拼接、组装。

if判断

需求:用户信息综合查询列表和用户信息查询列表总数这两个statement的定义使用动态sql。对查询条件进行判断,如果输入参数不为空才进行查询条件拼接。

if判断类似于JSTL.

mapper.xml



maper.java

/**
 * 用户信息的综合查询
 */
List findUserList(UserQueryVO userQueryVO) throws Exception;

/**
 * 用户信息的综合查询总数
 */
int findUserCount(UserQueryVO userQueryVO) throws Exception;

测试类

@Test
public void testfindUserList() throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    
    // 取消部分查询条件
    UserQueryVO userQueryVO = new UserQueryVO();
    UserCustom userCustom = new UserCustom();
//        userCustom.setSex("1");
    userCustom.setUsername("明");
    userQueryVO.setUserCustom(userCustom);
    List users = userMapper.findUserList(userQueryVO);
    System.out.println(users);
}

@Test
public void testFindUserCount() throws Exception{
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    
    // 取消部分查询条件
    UserQueryVO userQueryVO = new UserQueryVO();
    UserCustom userCustom = new UserCustom();
    userCustom.setSex("1");
//        userCustom.setUsername("明");
    userQueryVO.setUserCustom(userCustom);
    int count = userMapper.findUserCount(userQueryVO);
    System.out.println(count);
}
sql片段

以上的代码中mapper.xml中的SQL语句有一些重复,我们可以将上面的动态SQL中共有的部分抽取出来形成自己的sql片段,在其他的Statement中就可以使用此SQL片段。

定义并使用SQL片段



    
        
            AND user.sex = #{userCustom.sex}
        
        
            AND user.username LIKE "%${userCustom.username}%"
        
    

for-each

向sql传递数组或List,mybatis使用foreach解析。

例如:在用户查询列表和查询总数的statement中增加多个id输入查询。
两种方法:

SELECT * FROM USER WHERE id=1 OR id=10 OR id=16

SELECT * FROM USER WHERE id IN(1,10,16)

在输入参数类型中添加List ids传入多个id(并添加getter和setter)

修改mapper.xml中的SQL片段



    
        
            AND user.sex = #{userCustom.sex}
        
        
            AND user.username LIKE "%${userCustom.username}%"
        
    
    
        
         
        
            
            id=#{user_id}
        
    

测试类:

@Test
public void testfindUserList() throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    
    UserQueryVO userQueryVO = new UserQueryVO();
    UserCustom userCustom = new UserCustom();
    userCustom.setUsername("明");
    
    // 传入多个id
    List ids = Arrays.asList(1,16,22,25);
    userQueryVO.setId(ids);
    userQueryVO.setUserCustom(userCustom);
    List users = userMapper.findUserList(userQueryVO);
    System.out.println(users);
}


        每个遍历需要拼接的串
        #{user_id}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/64550.html

相关文章

  • 通过项目逐步深入了解Mybatis<>

    摘要:的实例不能共享使用,它也是线程不安全的。因此最佳的范围是请求或方法范围定义局部变量使用。在中就不需要直接对数据库的连接参数进行硬编码了。 转载请务必注明出处,原创不易! 相关文章:通过项目逐步深入了解Mybatis 本项目全部代码地址:Github-Mybatis Mybatis 解决 jdbc 编程的问题 1、 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用...

    BigTomato 评论0 收藏0
  • JAVA学习笔记036-第一个简单Mybatis程序(代码经验证)

    摘要:目标创建一个简单的框架的程序,实现对数据库的读取操作。的核心配置文件核心配置文件,配置数据库连接信息事物等每一个都需要在核心配置文件中注册工具类获取第一步获取对象既然有了,顾名思义,我们可以从中获得的实例。 ...

    itvincent 评论0 收藏0
  • Mybatis常见面试题

    摘要:执行没有,批处理不支持,将所有都添加到批处理中,等待统一执行,它缓存了多个对象,每个对象都是完毕后,等待逐一执行批处理。 Mybatis常见面试题 #{}和${}的区别是什么? #{}和${}的区别是什么? 在Mybatis中,有两种占位符 #{}解析传递进来的参数数据 ${}对传递进来的参数原样拼接在SQL中 #{}是预编译处理,${}是字符串替换。 使用#{}可以有效的防止...

    liuchengxu 评论0 收藏0
  • Java面试前需要了解东西

    摘要:我在面试前针对基础也花了不少的时间,期间也将自己写过的博文粗略地刷了一遍,同时也在网上找了不少比较好的资料部分是没看完的。看面试题也是校验自己是否真正理解了这个知识点,也很有可能会有新的收获。 一、前言 只有光头才能变强 回顾前面: 广州三本找Java实习经历 上一篇写了自己面试的经历和一些在面试的时候遇到的题目(笔试题和面试题)。 我在面试前针对Java基础也花了不少的时间,期间也将...

    renweihub 评论0 收藏0

发表评论

0条评论

plus2047

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<