欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

MyBatis入门学习(四)——ThreadLocal、缓存和多表查询

程序员文章站 2022-05-24 10:26:17
...

MyBatis入门学习四

一、ThreadLocal

1.线程容器,给线程绑定一个Object内容,(匿名内部类中使用的值一定是定义final或者是形式上的final状态,防止外部修改内部变量)。只要线程不变就能随时取出该对象。只有在创建ThreadLocal的线程中才能获得线程的内容

final关键字修饰的类不能被继承,对象不能改变指向地址,基本数据类型不能修改值,但对象的内容可以被修改

final ThreadLocal<Integer> t1=new ThreadLocal<>();
		t1.set(999);
		t1.set(888);

		new Thread(()->{
			System.out.println(t1.get());
		}).start();
		System.out.println(t1.get());

结果:

main--->888
Thread-0--->null

在具体运用中可以使某些资源在同一线程的不同层面共享,如在web项目中,我们可以在serlvet中创建一个Sqlsession对象,在service层中使用该对象,在返回到filter时关闭该对象。

二、缓存

1.应用程序和数据库交互的过程是一个相对比较耗时的过程

2.缓存存在的意义让应用程序减少对数据库的访问,提升程序运行效率

3.MyBatis中默认开启SqlSession缓存,同一个SqlSession对象调用同一个查询方法时,会先访问内存,查找有没有对应的statement对象,没有则访问数据库,获取查询结果后将结果的statement对象放入内存中。当下次调用该select方法时就可以直接通过内存获取结果。在MyBatis中一个查询方法对应一个Statement对象。

4.缓存流程

  • 步骤一:去缓存区中查找是否存在Statement对象
  • 步骤二:如果有,返回结果
  • 步骤三:如果没有找到Statement对象,再去数据库获取数据
  • 步骤四:数据库返回结果
  • 步骤五:把查询结果放到对应的缓存区中
    MyBatis入门学习(四)——ThreadLocal、缓存和多表查询

5.SqlSessionFactory缓存又叫二级缓存,它的有效范围是同一的Factory对象创建的所有SqlSession都可以获取。当数据被频繁使用,很少被修改才允许使用。

可以在Mapper.xml中添加如下标签使得同一个factory下的session使用相同方法时能访问共享内存中的factory的缓存。当使用session.commit()或者close()方法后会把SqlSession缓存的对象刷新到SqlSessionFactory缓存区中,如果不写readOnly,需要将查询对应的实体类序列化。

<cache readOnly="true"></cache>

三、多表查询

1.实现方式

​ 1.业务装配。对两个表编写单表查询语句,在业务层把两个查询结构进行关联

​ 2.使用AutoMapping特性。在实现两表联合查询时通过别名完成映射。

​ 3.在Mapper.xml文件中设置resultMap标签实现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3kVc0JPt-1585052120458)(D:\TyporaPhoto\image-20200323220428466.png)]

2.resultMap标签

​ 1.< resultMap >标签写在mapper.xml中,有我们自行编写SQL查询结果与实体类的映射关系可以解决数据库列名与实体类属性名没有一一对应得问题。在未设置< resultMap >标签时默认使用Auto Mapping特性。

​ 2.创建好了< resultMap >标签后,在< select >标签中使用resultMap属性代替resultType属性,其中引用需要的< resultMap >标签id。

​ 3.使用resultMap实现单表映射的具体操作:

之前使用resultType,传入的是一个po类名:

<!-- 根据账号密码查询用户信息 -->
	<select id="queryAccount" resultMap="AccountMap" parameterType="Account">
		select * from t_account where account=#{account} and password=#{password}
	</select>

现在自定义一个resultMap:

<resultMap id="AccountMap" type="Account">
		<!-- 主键使用id标签配置映射关系-->
		<id column="id" property="id"/>
		<!-- 其他列使用result标签配置映射关系-->
		<result column="account_no" property="account"/>
		<result column="password" property="password"/>
		<result column="balance" property="balance"/>
		<result column="name" property="name"/>
	</resultMap>
	<!-- 根据账号密码查询用户信息 -->
	<select id="queryAccount" resultMap="AccountMap" parameterType="Account">
		select * from t_account where account=#{account} and password=#{password}
	</select>

colunm属性表示数据库中字段名,property属性表示对应实体类中的属性名。

在sorm中是直接将列名转化为驼峰从而反射调用对应的方法,这里可以读取配置文件获取列名对应的属性名,修改更方便,但全部设置起来更复杂,需要自动化mapping以下关系

4.使用resultMap实现关联单个对象(N+1方式)

  • N+1查询方式:先查询出某个表的的消息,再根据这个表中的某些信息作为条件去查询另一个表的信息

  • 与业务装配的区别:将在service层执行的结果整合由mybatis来完成

  • 实现步骤:

    • 假设在User类中包含一个状态信息的对象Freeze

      public class User{
      	private int id;
      	private String username;
      	private String password;
      	private int ifFreeze;
      	private Freeze freeze;
      }
      
      public class Freeze {
          private int id;
          private String state;
      }
      
    • 在FreezeMapper.xml中配置查询语句

       <select id="queryFreezeById" resultType="Freeze">
                  select * from t_freeze where id=#{0}
          </select>
      
    • 在UserMapper.xml中配置< resultMap >标签和查询语句

      <resultMap id="UserMap" type="User">
      		<!-- 主键使用id标签配置映射关系-->
      		<id column="id" property="id"/>
      		<!-- 其他列使用result标签配置映射关系-->
      		<result column="username" property="username"/>
      		<result column="password" property="password"/>
      		<result column="if_freeze" property="ifFreeze"/>
      		<!--关联一个对象-->
      		<association property="freeze" select="com.xxbb.mapper.FreezeMapper.queryFreezeById" column="if_freeze">
      
      		</association>
      	</resultMap>
      	<select id="queryUser" resultMap="UserMap" parameterType="map">
      		select * from t_user
      		<if test="pageStart!=null and pageSize!=null">
      			limit #{pageStart},#{pageSize}
      		</if>
      	</select>
      
    • 调用方法执行queryUser的查询,结果如下,可以看到User对应的Freeze信息也查询出来了
      MyBatis入门学习(四)——ThreadLocal、缓存和多表查询

  • association属性说明

    proerty:关联的对象

    select:查询该对象信息时使用的方法

    colunm:把当前resultMap属性中哪一个字段的值作为参数传给该查询方法。

  • resultMap标签优化

    reusultMap中如果不设置字段名和属性名的映射关系则会使用Auto Mapping自动映射。此时仍然可以正常查询

    <resultMap id="UserMap" type="User">
    		<!--关联一个对象-->
    		<association property="freeze" select="com.xxbb.mapper.FreezeMapper.queryFreezeById" column="if_freeze">
    
    		</association>
    	</resultMap>
    	<select id="queryUser" resultMap="UserMap" parameterType="map">
    		select * from t_user
    		<if test="pageStart!=null and pageSize!=null">
    			limit #{pageStart},#{pageSize}
    		</if>
    	</select>
    

5.使用resultMap实现关联集合对象(N+1方式)

​ 1.在Freeze类中添加一个集合,用来存放对应状态的账号集合

public class Freeze {
    private int id;
    private String state;
    private List<?> userList;
}

​ 2.UserMapper.xml中添加查询语句

<select id="queryUserByFreeze" resultType="User">
		select * from t_user
		<where>
			<if test="param1!=null">
				and if_freeze=#{param1}
			</if>
		</where>
	</select>

​ 3.在FreezeMapper.xml中添加resultMap标签和查询语句

 <resultMap id="freezeMap" type="Freeze">

 <collection property="userList" column="id" select="com.xxbb.mapper.UserMapper.queryUserByFreeze">

        </collection>
</resultMap>
<select id="queryFreezeById" resultMap="freezeMap">
            select * from t_freeze where id=#{0}
</select>

​ 4.查询结果:

MyBatis入门学习(四)——ThreadLocal、缓存和多表查询

6.使用resultMap实现加载集合数据(联合查询方式)

​ 1.在< resultMap>中使用设置号列名和属性名的映射关系,再设置< collection >标签并为标签指定存储类型,在标签内设置查询结果的列名与实体类属性的映射关系。

<resultMap id="freezeMap" type="Freeze">
        <id column="fid" property="id"/>
<!--        <association property="userList" column="id" select="com.xxbb.mapper.UserMapper.queryUserByFreeze">-->
        <result column="state" property="state"/>
<!--        </association>-->
        <collection property="userList" ofType="User">
                <id column="uid" property="id"/>
                <result column="username" property="username"/>
                <result column="password" property="password"/>
        </collection>
    </resultMap>
    <select id="queryFreezeById" resultMap="freezeMap">
            select f.id as fid ,state,u.id as uid,u.username,password,if_freeze  from t_freeze as f left outer join t_user as u on u.if_freeze=f.id where f.id=#{0}
    </select>

如进行上述联合查询,在数据库表中的显示结果:

MyBatis入门学习(四)——ThreadLocal、缓存和多表查询

在查询返回值中的结果:

MyBatis入门学习(四)——ThreadLocal、缓存和多表查询

可见mybatis对结果按我们的要求进行了整合,将上述查询到的后4个字段的所有信息根据查询主键的不同整合成一个集合。参照上方的数据库查询结果图,可以看到他将所有主键为fid=0的结果都整合起来了,以User类的形式存放到了userList中。
MyBatis入门学习(四)——ThreadLocal、缓存和多表查询

与 5中提到的N+1的方式不同的地方在于N+1的情况是进行了两次查询,将两次查询的两个结果集进行整合,而在联合主键的查询中是进行了一次查询,对一个结果集里面的指定字段的值进行了整合

进行联合查询时如果是查询像N+1那样的内容 User类里有Freeze 类型属性,我们进行联合查询查出结果时,可以使用’ Freeze.id ', ’ Freez.state '作为t_user和t_freeze联合查询中的列名,这样mybatis会自动识别将它们存放进一个Freeze中。