Mybatis | 03 Mybatis的两种使用方式

Mybatis系列文章

1. Mybatis实现DAO的代理开发

1.1 相关准备

1.1.1 持久层Dao接口

package org.example.dao;

public interface IUserDao{
    //保存操作
    void save(User user);
    
    //更新操作
    void update(User user);
    
    //删除操作
    void delete(Integer userId);   
    
    //不同的查询操作
    //查询所有
    List<User> findAll();
    
    //通过ID精确查询
    User findById(Integer userId);
    
    //通过名称模糊查询
    List<User> findByName(String username);
    
    //使用聚合函数查询
    Integer findTotal();
    
    //使用综合条件查询
    List<User> findByVo(QueryVo vo);
}

1.1.2 用户实体类

package org.example.domain;

public class User{
    private Integer id;
    private String username;
    private Date birthday;
    private Integer bonus;

   //省略了所有的get和set方法

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", birthday=" + birthday +
                ", bonus=" + bonus +
                '}';
    }
}

1.1.3 测试方法

  • 基本使用(以findAll方法为例)
//Mybatis的运行过程,
@Test
public void test() throws Exception{
    //读取配置文件
    //会有异常要抛一个异常
    InputStream in = Resources.getResourceAsStream(IUserDao.class);
    //使用SqlSessionFactoryBuilder创建SqlSessionFactory工厂
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build();
    //使用SqlSessionFactory生产获取SqlSession对象
    SqlSession session = factory.openSession();
    //创建Dao的代理对象
    IUserDao userDao = session.getMapper(IUserDao.class);
    
    //使用代理对象执行方法
    List<User> users = userDao.findAll();
    //后续处理省略
    
    //提交修改
    session.commit();
    //释放资源
    session.close();
    in.close(); 
}
  • 抽取重复代码后
//所要使用到的对象
private InputStream in = null;
private SqlSessionFactory factory = null;
private SqlSession session = null;
private IUserDao userDao = null;

@Before
//在测试方法之前执行,初始化以上的对象
public void init() throws Exception{
    in = Resources.getResourceAsStream(IUserDao.class);
    factory = new SqlSessionFactoryBuilder().build();
    session = factory.openSession();
    userDao = session.getMapper(IUserDao.class);
}

@After
//在测试方法之后执行,提交并释放资源
public void destroy() throws Exception{
    session.commit();
    session.close();
    in.close();
}

@Test
public void findAllTest(){
    List<User> users = userDao.findAll();
}
Tips 1.1:手动提交

AutoCommit被设置成了false,因此在执行DML语句后要手动提交否则就会回滚

  • 没有提交
  1. 运行代码

无commit

  1. 运行结果

未提交

  • 提交
  1. 运行代码

commit

  1. 运行结果

提交

1.2 实现CRUD操作(Most Important)

1.2.1 保存操作Create

<!--保存操作-->
<insert id="saveUser" parameterType="org.example.domain.User">
	insert into user(username,birthday,bonus)
    values(#{username},#{birthday},#{bonus});
</insert>
Tips 1.2:获取新增用户的ID

数据库提供的==方法 last_insert_id( )==可以返回新增记录的ID

  1. 在保存操作中增加 selectKey标签 配置
<insert id="saveUser" parameterType="org.example.domain.User">
    <!--在保存后获取新增用户的ID-->
    <!--keyProperty代表要返回的值对应实体类中属性的名称-->
    <!--keyColumn代表数据库中列的名称
    <!--order指定操作是插入操作前BEFORE还是插入操作后AFTER执行-->
    <selectKey keyProperty="id" keyColumn="id" resultType="INT" order="AFTER">
    	select last_insert_id();
    </selectKey>
	insert into user(username,birthday,bonus) 
    values(#{username},#{birthday},#{bonus});
</insert>

标签属性:

  • keyProperty属性 指定要返回的值对应实体类中的属性名称
  • keyColumn属性 指定返回数据库中列的名称
  • order属性 指定操作是插入操作前BEFORE还是插入操作后AFTER执行
  1. 测试
  • 没有获取ID操作
@Test
public void saveUserTest(){
    User user = new User();
    user.setUserName("abc");
    user.setUserBirthday(new Date());
    user.setUserBonus(1000);

    userDao.saveUser(user);
    System.out.println(user);
}

运行结果

没有返回id操作

  • 有获取ID操作
@Test
public void saveTest() {
    User user = new User();
    user.setUserName("abc");
    user.setUserBirthday(new Date());
    user.setUserBonus(1000);

    System.out.println("before:"+user);
    userDao.saveUser(user);
    System.out.println("after:"+user);
 }

运行结果

有返回之前

有返回之后

1.2.2 删除操作Delete

<!--删除操作-->
<delete id="deleteUser" parameterTyoe="int/Integer/java.lang.Integer">
	delete from user weher id=#{id};
</delete>

1.2.3 更新操作Update

<!--更新操作-->
<update id="updateUser" parameterType="org.example.domain.User">
	update user set username=#{username},birthday=#{birthday},bonus=#{bouns}
    where id=#{id};
</update>

1.2.4 查询操作Retrieve

1.2.4.1 查询所有
<!--查询所有-->
<select id="findAll" resultType="org.example.domain.User">
	select * from user;
</select>
1.2.4.2 通过ID精确查询
<!--通过ID精确查询-->
<select id="findById" parameterType="int" resultType="org.example.domain.User">
	select * from user where id=#{id};
</select>
1.2.4.3 通过名称模糊查询
<!--通过名称模糊查询-->
<select id="fingByName" parameterType="string" resultType="org.example.domain.User">
    select * from user where username like #{name}
	select * from user where username like '%${Value}%'
</select>
Tips 1.3:两种模糊查询方式
  1. 结果对比
  • 使用#{ }的模糊查询

模糊查询#{}

  • 使用${ }的模糊查询

模糊查询${}

  1. 分析:#{}和${}的对比
  • #{ }表示一个占位符,将传入的值赋值给PreparedStatement中的占位符

    可以接收基本类型或pojo类型值,当为基本类型时#{ }括号中可以为任意名称

<!--username为string类型数据,所以一下两条语句是等价的-->
select * from user where username like #{username};
select * from user where username like #{name};
  • ${ }表示拼接SQL字符串,将传入的值直接拼接在SQL中

    可以接收基本类型或pojo类型值,当为基本类型是${ }括号中只能为value

<!--这里就只能是value-->
select * from user where username like '%${value}%';
  1. 结论:更多使用#{ }占位符的形式,基于PreparedStatement安全性高
1.2.4.4 使用聚合函数查询
<!--使用聚合函数查询-->
<select id="findTotal" resultType="int">
	select count(id) from user;
</select>
1.2.4.5 使用综合条件查询

联系2.3.2

<!--使用综合条件查询-->
<select id="fingByVo" parameterType="org.example.domain.QueryVo" 
        resultType="org.example.domain.User">
    select * from user where username like #{user.username}
</select>

1.3 CRUD标签属性

1.3.1 常用属性

虽然对于不同的操作使用不同的标签但是标签中的属性基本相同

示例

<!--通过ID精确查询-->
<select id="findById" parameterType="int" resultType="org.example.domain.User">
	select * from user where id=#{id};
</select>
  • 使用id属性确定方法名,可以和mapper标签中的namespace一起定位方法

  • 使用value属性(位于两个标签之间的部分)指定是要执行的SQL语句,

    对于可变参数在写SQL语句时使用==#{属性}==占位符代替

  • 使用parameterType属性指定输入参数的类型,以便给可变参数赋值

  • 使用resultType属性指定返回值封装的类型

1.3.2 输入参数parameterType深入

  • 可以传递简单类型

  • 可以传递pojo对象

  • 可以传递pojo包装对象:有多个对象共同组合成查询的条件

pojo包装对象:将各个查询条件封装到QueryVo中作为属性合成一个综合的查询条件

//将各个查询条件封装到QueryVo中作为属性
package org.example.domain;

public class QueryVo{
    private User user;
    private OtherType other;
    
    public void setUser(User user){
        this.user = user;
    }
    
    public User getUser(){
        return user;
    }
    
    public void setOther(OtherType other){
        this.other = other;
    }
    
    public OtherType getOther(){
        return other;
    }
}
Tips 1.4:补充OGNL表达式

mybatis在配置时使用的就是OGNL表达式

Object Graphic Navigation Language:对象图导航语言

功能:通过对象的取值方法来获取数据,在写法上省略了get

  • 一般的写法:user.getAttribute( )
  • OGNL的写法:user.attribute

问题:在mybatis中直接使用"username而"不用"user."

原因:已经在ParameterType提供了属性所属的类

Tips 1.5:补充pojo对象

Plain Ordinary Java Object:简单的、普通的Java对象

内在含义:那些没有继承任何类、也没有实现任何接口,更没有被其它框架侵入的java对象

可以看做是作为支持业务逻辑或持久化逻辑的协助类

1.3.3 返回值封装深入

尽量保证一致可以减少不必要的麻烦

1.3.3.1 实体类中的属性名和数据库中的列名相同

resultType属性:支持基本类型和实体类类型结果集的封装

要求:实体类中的属性名和数据库中的列名必须相同

1.3.3.2 实体类中的属性名和数据库中的列名不相同
  • 更改后的用户实体类
public class User implements Serializable {

    private Integer userId;
    private String userName;
    private Date userBirthday;
    private Integer userBonus;

    //省略了所有的get和set方法

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", userName='" + userName + '\'' +
                ", userBirthday=" + userBirthday +
                ", userBonus=" + userBonus +
                '}';
    }
}

  1. 对于增删改操作而言要修改与实体类属性名有关的位置

主要包括:keyProperty、SQL语句占位符中的属性名

修改之前:

<insert id="saveUser" parameterType="org.example.domain.User">
    <selectKey keyProperty="id" keyColumn="id" resultType="INT" order="AFTER">
    	select last_insert_id();
    </selectKey>
	insert into user(username,birthday,bonus) 
    values(#{username},#{birthday},#{bonus});
</insert>

修改之后:

<insert id="saveUser" parameterType="org.example.domain.User">
    <selectKey keyProperty="userId" keyColumn="id" resultType="INT" order="AFTER">
    	select last_insert_id();
    </selectKey>
	insert into user(username,birthday,bonus) 
    values(#{userName},#{userBirthday},#{userBonus});
</insert>
  1. 对于查询操作有更大的问题

若仍使用resultType查询结果无法对应就无法封装相应的值

不一致

解决方案

  1. 在数据库的层面解决:给列名起别名

优劣势:执行效率高,但当查询语句较多时修改语句工作量大

<select id="findAll" resultMap="userMap">
    select id as userId,username as userName,birthday as userBirthday,bonus as userBonus
    from user;
</select>
  1. 使用MyBatis提供的解决方案resultMap标签:配置结果列名和实体类属性的对应关系

标签属性:

  • id属性给当前的resultMap一个唯一标识,在select标签中引用
  • type属性指定实体类的全限定类名

子标签:

  • id标签用于指定主键对应的字段
  • result标签用于指定非主键对应的字段

子标签属性:

  • property属性用于指定实体类属性的名称
  • column属性用于指定数据库的列名

优劣势:执行效率不高,但配置完成后所有的查询语句都可以使用便于开发

 <!--配置实体类和列名的对应关系-->
<resultMap id="userMap" type="com.yhn.domain.User">
    <!--主键对应字段-->
    <id property="userId" column="id"></id>
    <!--非主键-->
    <result property="userName" column="username"></result>
    <result property="userBirthday" column="birthday"></result>
    <result property="userBonus" column="bonus"></result>
</resultMap>

<select id="findAll" resultMap="userMap">
    select * from user;
 </select>

2. Mybatis实现DAO的传统开发

2.1 实现CRUD操作

  • 配置文件

不再使用代理对象而是自己编写实现类,但还是要和代理一样编写相同的配置文件

  • 持久层DAO接口
package org.example.dao;

public interface IUserDao{
    //保存操作
    void saveUser(User user);
    
    //更新操作
    void updateUser(User user);
    
    //删除操作
    void deleteUser(Integer userId);   
    
    //不同的查询操作
    //查询所有
    List<User> findAll();
    
    //通过ID精确查询
    User findById(Integer userId);
    
    //通过名称模糊查询
    List<User> findByName(String username);
    
    //使用聚合函数查询
    Integer findTotal();
}
  • DAO接口实现类
package org.example.dao.impl;

public class UserDaoImpl implements IUserDao{
    private SqlSessionFactory factory;
    
    public UserDaoImpl(SqlSessionFactory factory){
		this.factory = factory;
    }
    
    //参数就是能够获取配置文件信息的key
    public List<User> findAll(){
		SqlSession session = factory.openSession();
        List<User> users = session.selectList("org.example.dao.IUserDao.findALL");
        session.close();
        return users;
    }
    
    public User findById(Integer userId){
        SqlSession session = factory.openSession();
        User user = session.selectOne("org.example.dao.IUserDao.findById",userId);
        session.close();
        return user;
    }
    
    public void saveUser(User user){
        SqlSession session = factory.openSession();
        session.insert("org.example.dao.IUserDao.saveUser",user);
        session.commit();
        session.close();
    }
    
    public void updateUser(User user){
        SqlSession session = factory.openSession();
        session.update("org.example.dao.IUserDao.updateUser",user);
        session.commit();
        session.close();
    }
    
    public void deleteUser(Integer userId){
        SqlSession session = factory.openSession();
        session.delete("org.example.dao.IUserDao.deleteUser",userId);
        session.commit();
        session.close();
    }
    
    //剩余的查询方法省略..
} 

Tips 2.1 :传统开发方式的分析

  1. 编写实现类的方式调用了SqlSession类中提供的方法来完成CRUD操作

    如:selectList( )、selectOne( )、insert( )、update( )、delete( )

  2. 方法中的参数就是能获取配置文件信息的键值,即配置文件中的namespace属性+id属性

  • 测试方法
//所要使用到的对象
private InputStream in = null;
private SqlSessionFactory factory = null;
//已经在实现类中定义了
//private SqlSession session = null;
private IUserDao userDao = null;

@Before
//在测试方法之前执行,初始化以上的对象
public void init() throws Exception{
    in = Resources.getResourceAsStream(IUserDao.class);
    factory = new SqlSessionFactory().build(in);
    userDao = UserDaoImpl(factory);
}

@After
//在测试方法之后执行,提交并释放资源
public void destroy() throws Exception{
    in.close();
}

@Test
public void findAllTest(){
    List<User> users = userDao.findAll();
}

2.2 与代理开发的对比

  • 传统实现类的方式仍然要编写配置文件,同时还要自己编写实现类

  • 而代理开发只需要编写配置文件,实现方式在获取Mapper接口后由框架完成,更便于开发

结论:因此更多的使用代理的方式完成CRUD的操作

2.3 后续

这种编写实现类的方式可以更好地去分析mybatis的源码进而了解其工作原理,后续部分再做记录

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值