JavaEE程序设计【九】

6、MyBatis的核心对象

(1)SqlSessionFactory

SqlSessionFactory是MyBatis框架中十分重要的对象,它是单个数据库映射关系经过编译后的内存镜像,其主要作用是创建SqlSession。

SqlSessionFactory对象的实例可以通过SqlSessionFactoryBuilder对象来构建,而SqlSessionFactoryBuilder则可以通过XML配置文件或一个预先定义好的Configuration实例构建出SqlSessionFactory的实例。

  • 构建SqlSessionFactory

    • 1
      2
      InputStream inputStream = Resources.getResourceAsStream("配置文件位置");
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    • SqlSessionFactory对象是线程安全的,它一旦被创建,在整个应用执行期间都会存在。

    • 如果我们多次的创建同一个数据库的SqlSessionFactory,那么此数据库的资源将很容易被耗尽。为此,通常每一个数据库都会只对应一个SqlSessionFactory,所以在构建SqlSessionFactory实例时,建议使用单例模式。

(2)SqlSession

SqlSession是MyBatis框架中另一个重要的对象,它是应用程序与持久层之间执行交互操作的一个单线程对象,其主要作用是执行持久化操作。

每一个线程都应该有一个自己的SqlSession实例,并且该实例是不能被共享的。同时,==SqlSession实例是线程不安全的==,因此其使用范围最好在一次请求或一个方法中,绝不能将其放在一个类的静态字段、实例字段或任何类型的管理范围(如Servlet的HttpSession)中使用。

  • 获取SqlSession session = sqlSessionFactory.openSession();

  • 使用完SqlSession对象后要及时关闭,通常可以将其放在finally块中关闭。

    • 1
      2
      3
      4
      5
      6
      SqlSession sqlSession = sqlSessionFactory.openSession();
      try {
      // 此处执行持久化操作
      } finally {
      sqlSession.close();
      }
  • SqlSession中的方法

    • 查询方法:

      1
      2
      3
      4
      5
      6
      7
       <T> T selectOne(String statement);
      <T> T selectOne(String statement, Object parameter);
      <E> List<E> selectList(String statement);
      <E> List<E> selectList(String statement, Object parameter);
      <E> List<E> selectList(String statement, Object parameter,
      RowBounds rowBounds);
      void select(String statement, Object parameter, ResultHandler handler);
    • 插入、更新和删除方法:

      1
      2
      3
      4
      5
      6
      int insert(String statement);
      int insert(String statement, Object parameter);
      int update(String statement);
      int update(String statement, Object parameter);
      int delete(String statement);
      int delete(String statement, Object parameter);
    • 其他方法:

      1
      2
      3
      4
      5
      void commit(); 提交事务的方法。
      void rollback(); 回滚事务的方法。
      void close(); 关闭SqlSession对象。
      <T> T getMapper(Class<T> type); 返回Mapper接口的代理对象。
      Connection getConnection(); 获取JDBC数据库连接对象的方法
  • 为了简化开发,通常在实际项目中都会使用工具类来创建SqlSession:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory = null;
    static {
    try {
    Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
    sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    } catch (Exception e) { e.printStackTrace(); }
    }
    public static SqlSession getSession() {
    return sqlSessionFactory.openSession();
    }
    }

    线程安全问题:

    例如:

    服务器中的一个公共计数变量,调用某个方法就加一次。

    多个用户同时访问,同时读取为5 +1后更新为6。造成错误数据。线程不安全。

    线程安全是通过线程同步控制来实现的,也就是synchronized关键字。==可以保证一个方法一段时间内只能被一个线程使用。==

7、MyBatis Generator

MyBatis Generator 超详细配置

8、动态SQL

MyBatis动态SQL(认真看看, 以后写SQL就爽多了)

真正的Mybatis动态sql — MyBatis Dynamic SQL

MyBatis 令人喜欢的一大特性就是动态 SQL。 在使用 JDBC 的过程中, 根据条件进行 SQL 的拼接是很麻烦且很容易出错的。 MyBatis 动态 SQL 的出现, 解决了这个麻烦。

MyBatis通过 OGNL 来进行动态 SQL 的使用的。

(1)if

在MyBatis中,<if>元素是最常用的判断语句,它类似于Java中的if语句,主要用于实现某些简单的条件选择。

例如:使用<if>元素对username和jobs进行非空判断,并动态组装SQL

1
2
3
4
5
6
7
    select * from customer where 1=1 
<if test="username !=null and username !=''">
and username like concat('%',#{username}, '%')
</if>
<if test="jobs !=null and jobs !=''">
and jobs= #{jobs}
</if>

if判断中的username是传入的参数。

(2)choose、when、otherwise

1
2
3
4
5
6
7
8
9
10
11
12
select * from customer where 1=1
<choose>
<when test="username !=null and username !=''">
and username like concat('%',#{username}, '%')
</when>
<when test="jobs !=null and jobs !=''">
and jobs= #{jobs}
</when>
<otherwise>
and phone is not null
</otherwise>
</choose>

使用<choose>及其子元素依次对条件进行非空判断,并动态组装SQL。

相当于 switch case default。和if 的区别在于其找到一个就停止判断之后的选项了。

(3)where、trim

在前两个小节的案例中,映射文件中编写的SQL后面都加入了“where 1=1”的条件,那么到底为什么要这么写呢?如果将where后“1=1”的条件去掉,那么MyBatis所拼接出来的SQL将会如下所示:

select * from customer where and username like concat('%',?, '%')

可以看出上面SQL语句明显存在SQL语法错误,而加入了条件“1=1”后,既保证了where后面的条件成立,又避免了where后面第一个词是and或者or之类的关键词。不过“where 1=1”这种写法对于初学者来将不容易理解,并且也不够雅观。

针对上述情况中“where 1=1”,在MyBatis的SQL中就可以使用<where><trim>元素进行动态处理。

  • where处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    select * from customer
    <where>
    <if test="username !=null and username !=''">
    and username like concat('%',#{username}, '%')
    </if>
    <if test="jobs !=null and jobs !=''">
    and jobs= #{jobs}
    </if>
    </where>
  • trim处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    select * from customer
    <trim prefix="where" prefixOverrides="and">
    <if test="username !=null and username !=''">
    and username like concat('%',#{username}, '%')
    </if>
    <if test="jobs !=null and jobs !=''">
    and jobs= #{jobs}
    </if>
    </trim>
    • prefix: 当 trim 元素包含有内容时, 增加 prefix 所指定的前缀
    • prefixOverrides: 当 trim 元素包含有内容时, 去除 prefixOverrides 指定的 前缀
    • suffix: 当 trim 元素包含有内容时, 增加 suffix 所指定的后缀
    • suffixOverrides: 当 trim 元素包含有内容时, 去除 suffixOverrides 指定的后缀

(5)set

在Hibernate框架更新:发送所有的字段给持久化对象,运行效率差。

在MyBatis中可以使用动态SQL中的<set>元素进行处理

例如:使用<set><if>元素对username和jobs进行更新判断,并动态组装SQL。这样就只需要传入想要更新的字段即可。

1
2
3
4
5
6
7
8
9
10
11
12
<update id="updateCustomer"  parameterType="cn.edu.c.po.Customer">
update customer
<set>
<if test="username !=null and username !=''">
username=#{username},
</if>
<if test="jobs !=null and jobs !=''">
jobs=#{jobs},
</if>
</set>
where id=#{id}
</update>

(6)foreach

假设在一个客户表中有1000条数据,现在需要将特定id值的客户信息全部查询出来,这要怎么做呢?

  • 一条一条的查询
    • 那如果要查询1000条数据呢,岂不是很累?
  • 在Java中用for循环查询
    • 考虑过N条查询语句时的查询效率了吗?

针对上述需求,理想的解决方法就是使用MyBatis中动态SQL的<foreach>元素进行处理。其基本使用示例如下所示:

1
2
3
4
5
6
7
8
9
<select id="selectByStudentIdList" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from student
where student_id in
<foreach collection="list" item="id" open="(" close=")" separator="," index="i">
#{id}
</foreach>
</select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void selectByStudentIdList() {
SqlSession sqlSession = null;
sqlSession = sqlSessionFactory.openSession();
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);

List<Integer> ids = new LinkedList<>();
ids.add(1);
ids.add(3);

List<Student> students = studentMapper.selectByStudentIdList(ids);
for (int i = 0; i < students.size(); i++) {
System.out.println(ToStringBuilder.reflectionToString(students.get(i), ToStringStyle.MULTI_LINE_STYLE));
}

sqlSession.commit();
sqlSession.close();
}

生成的sql和运行结果如下:

  • item:变量名。 即从迭代的对象中取出的每一个值.配置的是循环中当前的元素。
    • 变量名。 即从迭代的对象中取出的每一个值
  • index:配置的是当前元素在集合的位置下标。
    • 索引的属性名。 当迭代的对象为 Map 时, 该值为 Map 中的 Key.
  • collection: 必填, 集合/数组/Map的名称.配置的list是传递过来的参数类型(首字母小写),它可以是一个array、list(或collection)、Map集合的键、POJO包装类中数组或集合类型的属性名等。
    • collection属性,该属性是必须指定的,而且在不同情况下,该属性的值是不一样的。主要有以下3种情况
      • 如果传入的是单参数且参数类型是一个数组或者List的时候,collection属性值分别为array和list(或collection)。
        • 推荐: 使用 @Param 来指定参数的名称, 如我们在参数前@Param(“ids”), 则就填写 collection=ids
      • 如果传入的参数是多个的时候,就需要把它们封装成一个Map了,当然单参数也可以封装成Map集合,这时候collection属性值就为Map的键。
        • 多参数请使用 @Param 来指定, 否则SQL中会很不方便
      • 如果传入的参数是POJO包装类的时候,collection属性值就为该包装类中需要进行遍历的数组或集合的属性名。
  • open和close:配置的是以什么符号将这些集合元素包装起来。
    • 循环开头的字符串和循环结束的字符串
  • separator:配置的是各个元素的间隔符。

(7)bind

select * from customer where username like '%${value}%'

上述SQL语句有什么不妥?

  • 如果使用“${}”进行字符串拼接,则无法防止SQL注入问题;
  • 如果改用concat函数进行拼接,则只针对MySQL数据库有效;
  • 如果改用“||”进行字符串拼接,则只针对Oracle数据库有效。

这样,映射文件中的SQL就要根据不同的情况提供不同形式的实现,这显然是比较麻烦的,且不利于项目的移植。为了减少这种麻烦,就可以使用MyBatis的<bind>元素来解决这一问题。

MyBatis的<bind>元素可以通过OGNL表达式来创建一个上下文变量,其使用方式如下:

1
2
3
4
5
6
7
<select id="findCustomerByName" parameterType="Integer"
resultMap="BaseResultMap">
<bind name="pattern_username" value="'%'+_parameter.getUsername()+'%'" />
select * from customer
where
username like #{pattern_username}
</select>
  • _parameter.getUsername()表示传递进来的参数(也可以直接写成对应的参数变量名,如username)
-----------------------本文结束 感谢阅读-----------------------
坚持原创技术分享,您的支持将鼓励我继续创作!恰饭^.^~