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 < #{age}
<if test="name !=null">
and name like #{name}
</if>
</select>
注意:mapper中,小于号、大于号等需要使用转义字符代替
小于号 | < |
---|---|
待补充
标签< 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提供啦复杂的1对1,1对多,多对多查询映射
在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),从而完成了订单到商品的多对多关系。