mybatis学习笔记

一级目录

要点1

使用sqlSession对数据库进行删、改、增的操作时,一定要将参数设为true;如下:

SqlSession sqlSession = factory.openSession(true);

要点2:使用mybatis动态代理(面试考点)

1.首先把接口写好,如IUserDao.java
2.把mapper写好,如UserMapper.xml (面试考点)
其中注意四个个坑:一是namespace为包名+接口名,如下

	<mapper namespace="com.dao.IUserDao">

二是每个sql的id必须与接口方法名一致,如下

	<insert id="addNotice" parameterType="com.domain.Notice">

三是被代理接口中的方法参数尽量(复杂类型要一致,基本类型也不强求)和对应标签的paramterType一致(也可以忽略,让框架自己检测)
四是被代理接口中的方法的输出参数类型必须与对应标签的resultType一致

3, 在测试类@Test(实际上是为来的Service)中,需要调用Dao,但是这里的Dao并不是new出来的,而是通过sqlSession.getMapper(IUserDao.class)得到的。

	@Test
    public void selectUser(){
        SqlSession sqlSession = factory.openSession(true);
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);
        Notice user = userDao.findUser(2);
        System.out.println(user);
        sqlSession.close();
    }

要点3:使用hashMap

如果想向数据库传多个参数,可以使用map的方式。如制作登录功能,需要向数据库传入string 账号,string 密码;可以如下操作

在IUserDao.java文件中:

	public User loginUser(Map<String,String> map);

在IUserDao.xml文件中:

	<selcet id="LoginUser" parameterType="hashMap" resultType="user">
		select * from where user_name=#{username} and password=#{password}
	</select>

测试文件中:

	@Test
    public void loginUser(){
        SqlSession sqlSession = factory.openSession(true);
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);
        Map<string,string> map = new HashMap<string,string>();
        map.put("username","admin");
        map.put("password","123");
		User user = UserDao.loginUser(map);
    }

但要注意细节:xml文件中#{}内的参数名应与测试文件中map的key相同。

要点3:接口方法接受多个参数

方法一:使用mybatis规定参数名

接口方法接受多个参数时,其参数顺序有要求,接口参数应写为
(注意参数名param,顺序为倒序)

	public User loginUser(String param1,String param0);

xml文件中应写为
(注意参数名arg,顺序为倒序)

	<selcet id="LoginUser" parameterType="hashMap" resultType="user">
		select * from where user_name=#{arg1} and password=#{arg0}
	</select>

但这个方法不建议。

方法二(建议使用):使用注解@param

使用注解的方法,如下:

	public User loginUser(@Param("username") String username, @Param("password") String password);
	<selcet id="LoginUser" parameterType="hashMap" resultType="user">
		select * from where user_name=#{username} and password=#{password}
	</select>

注意,注解内的参数名应与sql语句占位符中的参数名一致,而接口的形参名称无所谓

要点4:insert语句的两个设置,用于回显id(面试考点)

如果在插入表项的同时,希望获得sql自增的id,则需要在xml的insert语句中,进行两个设置。如下:

	<insert id="LoginUser" parameterType="user" useGeneratedKeys="true" keyProperty="id">
		insert into user(username,password) values (#{username},#{password})
	</select>

keyProperty对应的“”即为数据库中的id表项。

要点5:#{}与${}的区别(面试考点)

#{}实现的是sql语句的预处理,之后执行sql中用?代替,mybatis会自动实现数据类型转换,并防止sql注入
${}用于实现sql的拼接操作,不做数据类型转换,不能防止sql注入。

个人理解,使用${}时,会直接将参数代替 ${}。而使用#{}时,由mybatis进行替换操作。
一般常用#{},但是使用mybatis-config.xml中的property标签进行最基础的数据库配置时需要使用 ${}
在这里插入图片描述

要点6:sql片段

含义:使用sql片段代替常用sql
使用场景:如面对sql中查询操作的语句:select * from…;实际操作中,为了进行sql优化,常将*改为具体的列名(这也是最简单的优化),但是这个实际上写起来很繁琐,故可用sql片段;

<!--定义部分公共的sql片段,在别的sql语句中引用-->
<sql id="commonUser">
	id,user_name,name,password,sex,birthday,created
</sql>

<!--使用sql片段-->
<select id="findUser">
	select <include refid="commonUser"></include> from user where id = #{id}
</select>

要点7:动态sql

动态sql即动态拼接sql;

标签‘ < if >’

例如:查找年龄 < 25且根据姓名中的字模糊查询
sql语句为:

	select * from user where age < 25 and name like %%

传统方法,需要判断传入参数是否含 “字” ,从而决定是否使 sql 语句加上 and 后的语句。
而 mybatis 使用 “<if> </if>”标签进行动态拼接,代码如下

<select id="findUser" resultType="user">
	select * from user where age &lt; #{age}
	<if test="name !=null">
		and name like #{name}
	</if>
</select>

注意:mapper中,小于号、大于号等需要使用转义字符代替

小于号&lt;

待补充

标签< choose > < when > < otherwise >

类似switch

<select id="findUser" resultType="user">
	select * from user where sex = 1
	<choose>
		<when test="age!=null">
			and age %lt; #{age}
		</when>
		<when test="age!=null and name !=null">
			and name like #{name}
		</when>
		<otherwise>
			and password = #{password}
		<otherwise>
	</choose>
</select>

标签< where >

案例:查询用户,如果输入的性别就按照性别查询,如果输入名字就按照名字查询,如果都没有就查询所有。

<select id="findUser" resultType="user">
	select * from user 
	<where>
		<!--第一个if标签后的语句前可以加and,运行时若仅触发第一个if,mybatis会自动去除该and-->
		<if test = "sex != null">
			sex = #{sex}
		</if>
		<if test = "name != null">
			and name like #{name}
		</if>
	</where>
</select>

标签< set >

用于update,实现传入什么参数,改什么参数;

    <update id="updateuser" parameterType="User">
        update User
        <set>
	        <if test = "userName!=null and userName!=''">
	        	user_name=#{userName},
	        </if>
	        <if test = "password!=null and password!=''">
	        	password=#{password},
	        </if>
		</set>
        where id = #{id}
    </update>

注意if语句后面需要加逗号,这是 update 语句要求的

标签< foreach >

可以提供一系列的值;
使用场景:如购物车中选中几条记录,想要购买时
select * from user where id in {1,2,3}
xml:

<select id="findUser" resultType="user">
	select * from user where id in
	<!--foreach标签完成拼接sql时,给出列表数据
		collection:列表所在的容器

	-->
	<foreach collection="ids" item="id" open="(" close=")" separator=",">
		#{id}
	</foreach>
</select>

接口:

public List<User> findUser(@Param("ids") List<Integer> ids);

测试类:

    @Test
    public void selectAll(){
        SqlSession sqlSession = factory.openSession(true);
        IUserDao UserDao = sqlSession.getMapper(IUserDao.class);
        List<Integer> ids = new ArrayList<Integer>();
        Collections.addAll(ids,1,2,3,4);
        List<User> users = userDao.findAllUser(ids);
        for (User user : allUser) {
            System.out.println(user);
        }
        sqlSession.close();
    }

要点8:sql缓存

一级缓存

一级缓存 Mybatis的一级缓存是指SQLSession,一级缓存的作用域是SQlSession, Mabits默认开启一级缓存。 在同一个SqlSession中,执行相同的SQL查询时;第一次会去查询数据库,并写在缓存中,第二次会直接从缓存中取。 当执行SQL时候两次查询中间发生了增删改的操作,则SQLSession的缓存会被清空。 每次查询会先去缓存中找,如果找不到,再去数据库查询,然后把结果写到缓存中。 Mybatis的内部缓存使用一个HashMap,key为hashcode+statementId+sql语句。Value为查询出来的结果集映射成的java对象。 SqlSession执行insert、update、delete等操作commit后会清空该SQLSession缓存。

1、一级缓存的生命周期有多长?

a、MyBatis在开启一个数据库会话时,会 创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象。Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。

b、如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。

c、如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用。

d、SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用

2、怎么判断某两次查询是完全相同的查询?

mybatis认为,对于两次查询,如果以下条件都完全一样,那么就认为它们是完全相同的两次查询。

a 传入的statementId

b 查询时要求的结果集中的结果范围

c. 这次查询所产生的最终要传递给JDBC java.sql.Preparedstatement的Sql语句字符串(boundSql.getSql() )

d 传递给java.sql.Statement要设置的参数值
  举例: 按公告id查公告表

    @Test
    public void selectNotice(){
        SqlSession sqlSession = factory.openSession(true);
        INoticeDao noticeDao = sqlSession.getMapper(INoticeDao.class);
        //调用接口中的方法(本质是mybatis在通过数据库连接以及执行器去只想sql)
        Notice notice = noticeDao.findNotice(2);
        System.out.println(notice);

		//下面的操作并没有真正执行sql,而是使用一级缓存
		INoticeDao noticeDao2 = sqlSession.getMapper(INoticeDao.class);
		Notice notice2 = noticeDao2.findNotice(2);
        System.out.println(notice2);

		//选这个语句会返回true,说明俩个对象是相同的。 
		System.out.println(notice==notice2);
    }

二级缓存

二级缓存是mapper级别的,Mybatis默认是没有开启二级缓存的。 第一次调用mapper下的SQL去查询用户的信息,查询到的信息会存放代该mapper对应的二级缓存区域。 第二次调用namespace下的mapper映射文件中,相同的sql去查询用户信息,会去对应的二级缓存内取结果。
二级缓存可以跨越多个session,只要它们是同一个mapper下的namespace即可。

开启方法,在mapper.xml文件中,添加.

<cache/>

测试方法:

    @Test
    public void selectNotice(){
        SqlSession sqlSession = factory.openSession(true);
        INoticeDao noticeDao = sqlSession.getMapper(INoticeDao.class);
        //调用接口中的方法(本质是mybatis在通过数据库连接以及执行器去只想sql)
        Notice notice = noticeDao.findNotice(2);
        System.out.println(notice);
		
		sqlSession.close()
		//在关闭SQLSession对象时,发现开启二级缓存,需要将对象序列化(存储于二级缓存中)
		INoticeDao noticeDao2 = sqlSession.getMapper(INoticeDao.class);
		Notice notice2 = noticeDao2.findNotice(2);
        System.out.println(notice2);
		System.out.println(notice==notice2);
    }

但是运行会报错,Error serializing object, Cause:java.io.NotSerializableException:com.domain.Notice
此错误发生于第二次查找对象时,原因是在关闭SQLSession对象时,发现开启二级缓存,需要将对象序列化(存储于二级缓存中),而Notice类没有实现序列化接口,无法给二级缓存中储存。
解决方法:修改Notice类

public class Notice implements Serializable{
....
}

个人理解: 同一个mapper文件下,执行不同sql语句时,会先向二级缓存中查找,没有命中则查找database,并写入二级缓存;而一级缓存只针对于同一句sql,才会进入一级缓存查找

清空缓存

sqlSession.clearCache();

要点9:分页插件PageHelper

引入插件

在pom.xml文件中,引入

        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.1.10</version>
        </dependency>

在mybatis-config.xml文件中,<typeAliases>标签后引入

        <!--添加分页插件-->
        <plugins>
            <!-- com.github.pagehelper为PageHelper类所在包名 -->
            <plugin interceptor="com.github.pagehelper.PageInterceptor">

            </plugin>
        </plugins>

至此,引入完成

使用插件

测试代码:

    @Test
    public void selectAllNotice(){
        SqlSession sqlSession = factory.openSession(true);
        INoticeDao noticeDao = sqlSession.getMapper(INoticeDao.class);
        //执行查询之前,设置分页相关数据;pageNum为起始位置(从0开始算),pageSize为查询个数
        PageHelper.startPage(3,2);
        List<Notice> allUser = noticeDao.findAllNotice();
        for (Notice notice : allUser) {
            System.out.println(notice);
        }
    }

这是最简单的使用,可以查询到第4个数据与第5个数据两条数据。
使用PageInfo:PageInfo是专门封装分页后数据的类

    @Test
    public void selectAllNotice(){
        SqlSession sqlSession = factory.openSession(true);
        INoticeDao noticeDao = sqlSession.getMapper(INoticeDao.class);
        //执行查询之前,设置分页相关数据;pageNum为起始位置(从0开始算),pageSize为查询个数
        PageHelper.startPage(3,2);
        List<Notice> allUser = noticeDao.findAllNotice();
        //分页的详情信息对象,将上述查询到的数据allUser放入PageInfo
        PageInfo<Notice> info = new PageInfo(allUser);
        //总页数
        int pages = info.getPages();
        //当前页数
        int pageNum = info.getPageNum();
        //每页的总行数
        int pageSize = info.getPageSize();
        //当前页的实际数据,在PageInfo的父类中通过获取数据和总记录的变量
        List<Notice> list = info.getList();
		long total = info.getTotal();
        for (Notice notice : allUser) {
            System.out.println(notice);
        }
         
        //注意这里如果再次查询,结果会有变化
        List<Notice> notices = noticeDao.findAllNotice();
        //第一次查询结果仅有两条记录,而第二次查询结果会有所有记录。
        //原因在于分页仅对身后第一次查询有效
        sqlSession.close();
    }

小技巧,可以合并写,如下

        List<Notice> allUser = noticeDao.findAllNotice();
        PageInfo<Notice> info = new PageInfo(allUser);
        ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
        PageInfo<Notice> info = new PageInfo(noticeDao.findAllNotice());

要点10:1对1继承方式

需求:根据某个指定的订单找到对应的用户
每个订单对应唯一一个用户,每个用户对应多个订单。
如果采用继承的方法,可以抽象出一个新的类来继承订单或用户。
即,设计新类UserOrder继承User类,然后再UserOrder类中放入Order类的数据

public class UserOrder extends User implements Serializable{
	private Interger oid;
	private Long Userid;
	private String orderNumber;
	//get set方法
	...
}

根据某个指定的订单找到对应的用户,对应sql为

select *-from tb_order, tb_user where tb_order.id = tb_user.user_id and tb_order.id = ?;

dao层接口

public interface IUserDao2{
	public UserOrder getOrderTouser(Integer oid);
}

测试:

    @Test
    public void selectAllNotice(){
        SqlSession sqlSession = factory.openSession(true);
        INoticeDao userDao = sqlSession.getMapper(IUserDao2.class);
        UserOrder orderToUser = mapper.getOrderTOUser(1);
        System.out.println(orderToUser);
        sqlSession.close();
    }

xml文件:

<select id="getOrderToUser" resultType="userOrder">
	select * from tb_order, tb_user where tb_order.id = tb_user.user_id and tb_order.id = #{oid};
</select>

小瑕疵:当两个表的id都叫id时,会出现问题;解决方法:修改类中的名字,如UserOrder中的order的id改为oid,并在sql语句中,利用as方法起别名。

要点11:1对1类中引用(主流)

在类Order中添加User的引用如下(因为一个Order对应一个User,而User对应多个Order)
private User user;
xml文件:

<!--mybatis提供啦复杂的111对多,多对多查询映射
	在mapper中,返回结果不能使用resultType(因为它仅支持简单映射关系)
	
	复杂查询映射:在一个对象引用别的对象(list,单一对象),必须使用resultMap
	resultMap本质是个独立标签,用以封装复杂映射关系
	resultMap标签上的属性:
		id:给这个标签命名(唯一标识,需要在别的select标签上引用,不可重名)
		type:映射结果封装是的对象对应的类型
		autoMapping:查询出来的值自动映射
		
	resultMap标签中的子标签
		id:标识主查询中的主键将来映射到对象上的那个属性
			colunm:
		result:配置非主键的那些列和属性的对应关系
		
		association :配置主和次的一对一关系,主要用来配置次表中的映射关系
			property:主对象中应用次对象时设置的属性名
			JavaType:次对象所属的类型
		-->
	<resultMap id="orderUserMap" type="order" autoMapping="true">
		<id property="id" column="oid"/>
		<result property="orderNumber" column="order_number"/>
		
		<association property="user" JavaType="user" autoMapping="true">
			<id property="id" column="id"/>
			<result property="userName" column="user_name"/>
		</association>
	</resultMap>
	<select id="getOrderToUser2" resultMap="orderUserMap">
	select * from tb_order, tb_user where tb_order.id = tb_user.user_id and tb_order.id = #{oid};
	</select>

要点11:1对多

同要点10,只是在1 的类中引用多,使用List的方式

private List<order> orders; 

xml文件:

<!--
	一对多:需要在resultMap中使用collection配置多的映射
		-->
	<resultMap id="orderUserMap" type="order" autoMapping="true">
		<id property="id" column="oid"/>
		<result property="orderNumber" column="order_number"/>
		
		<collection property="user" ofType="user" autoMapping="true">
			<id property="id" column="id"/>
			<result property="userName" column="user_name"/>
		</collection >
	</resultMap>
	<select id="getOrderToUser2" resultMap="orderUserMap">
	select * from tb_order, tb_user where tb_order.id = tb_user.user_id and tb_order.id = #{oid};
	</select>

重点区别在于标签association 换为 collection;而JavaType换为ofType;
此处代码没好好写,不要在意。

要点11:多对多

多对多中间有个连接表,故只要将连接表中引用两边的类即可

<resultMap id="moreToMore" type="order" autoMapping="true">
	<id property="id" column="oid"/>
		<collection property="orderDetails" ofType="orderDetails" autoMapping="true">
			<id property="id" column="id"/>
			<result property="userName" column="user_name"/>
			<!--每个明细关联一个商品-->
			<association property="item" JavaType="Item" autoMapping="true">
				<id property="id" column="id"/>
				<result property="itemName" column="item_name"/>
			</association>
		</collection >

也即一个订单(order)对应多个详情表(orderDetails),而每个详情表对应一个商品(item),从而完成了订单到商品的多对多关系。

引入插件

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值