常见面试题:
1.ORM框架工作原理
1、以一定的映射方式,实体模型和数据库关系的映射
2、ORM框架启动时加载这些映射和数据库配置文件
3、ORM通过对最原生jdbc的封装提供更加便利的操作API
4、Dao通过ORM提供的便捷API以对象的方式操作数据库关系。
2.JDBC编程有哪些不足之处,MyBatis是如何解决这些问题的?
(1)mybatis是一个优秀的基于java的持久层框架,它内部封装了jdbc,使开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。
(2)mybatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。
3.MyBatis与Hibernate有哪些不同?
(1) Hibernate是一个完整的ORM框架,常规CRUD不需要写SQL语句,会自动生成SQL; MyBatis 并不是一个完整的ORM框架,因为我们还需要自己去写全部SQL.
(2)Mybatis 简单易学,程序员直接编写原生态sql,可控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套sql映射文件,工作量大。
(3)Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用hibernate开发可以节省很多代码,提高效率。但是Hibernate的缺点是学习门槛高,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡,以及怎样用好Hibernate需要具有很强的经验和能力才行。
4. mybatis的基本工作流程
1.读取配置文件(配置文件包含数据库连接信息和Mapper映射文件或者Mapper包路径)
Mybatis 读取XML配置文件后会将内容放在一个Configuration类中,Configuration类会存在整个Mybatis生命周期,以便重复读取。
2. SqlSessionFactoryBuilder会读取Configuration类中信息创建SqlSessionFactory。SqlSessionFactory的生命周期是程序级,程序运行的时候建立起来,程序结束的时候消亡。
3.SqlSessionFactory建立SqlSession,目的执行sql语句,SqlSession是过程级,一个方法中建立,方法结束应该关闭
4.当用户使用mapper.xml文件中配置的的方法时,mybatis首先会解析sql动态标签为对应数据库sql语句的形式,并将其封装进MapperStatement对象,然后通过executor将sql注入数据库执行,并返回结果。
5.将返回的结果通过映射,包装成java对象。
5. #{}和${}的区别是什么?
#{}是预编译处理,Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;防止SQL注入, 提高系统安全性.
${}是字符串替换。Mybatis在处理${}时,就是把${}替换成变量的值(没有 “ ” ),一般用与排序 和 分页场景。
6.当实体类中的属性名和表中的字段名不一样 ,怎么办 ?
第1种: 通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。
1 <select id=”selectorder” parametertype=”int” resultetype=”me.gacl.domain.order”> 2 select order_id id, order_no orderno ,order_price price form orders where order_id=#{id}; 3 </select>
第2种: 通过<resultMap>来映射字段名和实体类属性名的一一对应的关系.
1 <select id="getOrder" parameterType="int" resultMap="orderresultmap"> 2 select * from orders where order_id=#{id} 3 </select> 4 5 <resultMap type=”me.gacl.domain.order” id=”orderresultmap”> 6 <!–用id属性来映射主键字段–> 7 <id property=”id” column=”order_id”> 8 9 <!–用result属性来映射非主键字段,property为实体类属性名,column为数据表中的属性–> 10 <result property = “orderno” column =”order_no”/> 11 <result property=”price” column=”order_price” /> 12 </reslutMap> 13
7. 模糊查询like语句该怎么写?
8. 通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?
Dao接口,就是人们常说的Mapper接口,接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中MappedStatement的id值,接口方法内的参数,就是传递给sql的参数。Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MappedStatement,
举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到namespace为com.mybatis3.mappers.StudentDao下面id = findStudentById的MappedStatement。
在Mybatis中,每一个<select>、<insert>、<update>、<delete>标签,都会被解析为一个MappedStatement对象。
Dao接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略。
Dao接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Dao接口生成代理proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回。
9.Mybatis是如何进行分页的?分页插件的原理是什么?
Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页,可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
10.Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?
第一种是使用<resultMap>标签,逐一定义列名和对象属性名之间的映射关系。
第二种是使用sql列的别名功能,将列别名书写为对象属性名,比如T_NAME AS NAME,对象属性名一般是name,小写,但是列名不区分大小写,Mybatis会忽略列名大小写,智能找到与之对应对象属性名,你甚至可以写成T_NAME AS NaMe,Mybatis一样可以正常工作。
有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。
11.如何获取自动生成的(主)键值?
在xml映射器中配置useGeneratedKeys参数,设定keyProperty的值为在Entity里面的字段名,通过实体类对象的getId()方法得到对应的值就是数据库中的主键值。
12. mybatis一级缓存二级缓存
一级缓存
Mybatis在没有配置的默认情况下只开启一级缓存,一级缓存只是相对于同一个SqlSession而言。所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。
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对象的数据,但是该对象可以继续使用。
二级缓存:
SqlSessionFactory层面上的二级缓存默认是不开启的,二级缓存的开席需要进行配置,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的。 也就是要求实现Serializable接口,配置方法很简单,只需要在映射XML文件配置就可以开启缓存了<cache/>,如果我们配置了二级缓存就意味着:
- 映射语句文件中的所有select语句将会被缓存。
- 映射语句文件中的所欲insert、update和delete语句会刷新缓存。
- 缓存会使用默认的Least Recently Used(LRU,最近最少使用的)算法来收回。
- 根据时间表,比如No Flush Interval,(CNFI没有刷新间隔),缓存不会以任何时间顺序来刷新。
- 缓存会存储列表集合或对象(无论查询方法返回什么)的1024个引用
- 缓存会被视为是read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以安全的被调用者修改,不干扰其他调用者或线程所做的潜在修改。
参考mybatis面试总结:https://blog.csdn.net/a745233700/article/details/80977133
常见编程题 (涵盖:批量插入,批量删除)
1. SQL映射器Mapper接口
MyBatis基于代理机制,可以让我们无需再编写Dao的实现。直接把以前的dao接口定义成符合规则的Mapper。
注意事项:
① :接口必须Mapper结尾,名字是DomainMapper
② :mapper.xml文件要和Mapper接口建立关系,通过namespace:要能连接到Mapper接口
③ : User get(Long id)返回值,方法,参数类型等必须使:Mapper接口和mapper.xml保持一致
具体步骤代码如下:
1. 核心配置文件:mybatis-config.xml 及 db.properties
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 引入db.properties --> <properties resource="db.properties"></properties> <!-- 环境们的配置 --> <environments default="development"> <!--环境的配置: --> <environment id="development"> <!--使用jdbc方式管理事务 --> <transactionManager type="JDBC" /> <!-- 数据源的四大配置 --> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driverClassName}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </dataSource> </environment> </environments> <!-- 注册对象关系映射文件 --> <mappers> <mapper resource="com/gs/domain/UserMapper.xml"/> </mappers> </configuration>
db.properties 文件
jdbc.driverClassName = com.mysql.jdbc.Driver jdbc.url = jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8 jdbc.username = root jdbc.password = root
2. 创建sqlSession 对象的工具类:MyBatisUtils.java
package com.gs.tools; import java.io.IOException; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; public class MyBatisUtils { private static SqlSessionFactory factory= null; static { try { factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml")); } catch (IOException e) { e.printStackTrace(); } } public static SqlSession getSession(){ return factory.openSession(); } }
3. User.java 三个字段。
public class User { private Long id; private String username; private String password; ......getter setter 无参 有参,toString() }
4. UserMapper 接口
public interface UserMapper { //得到一个用户 User get(Long id); // 得到所有用户 List<User> getAll(); //批量删除用户 void deleteBatch(List<Long> list); //批量插入用户 void insertBatch(List<User> lists); }
5.UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!-- 使用sql映射器Mapper接口的方式 namespace的值 是 对应接口的全限定名称 --> <mapper namespace="com.gs.mapper.UserMapper"> <!-- 得到一个用户 --> <select id="get" parameterType="long" resultType="com.gs.domain.User"> select * from user where id = #{?} </select> <!--得到所有用户 --> <select id="getAll" resultType="com.gs.domain.User"> select * from user </select> <!--批量删除用户 --> <delete id="deleteBatch" parameterType="list"> delete from user where id in <foreach collection="list" index="index" open="(" separator="," close=")" item="ids"> #{ids} </foreach> </delete> <!-- 批量插入用户 --> <insert id="insertBatch" parameterType="list"> insert into user(id,username,password) values <foreach collection="list" item="item" index="index" separator=","> (#{item.id},#{item.username},#{item.password}) </foreach> </insert> </mapper>
6.测试类 testMybatis.java
package com.gs.test; import java.util.ArrayList; import java.util.List; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import com.gs.domain.User; import com.gs.mapper.UserMapper; import com.gs.tools.MyBatisUtils; public class testMybatis{ @Test public void getuser(){ SqlSession session = MyBatisUtils.getSession(); //获取UserMapper接口的对象 UserMapper userMapper = session.getMapper(UserMapper.class); //namespace = 接口全限定名称 +.+ 方法名 User user = userMapper.get(1L); System.out.println(user); } @Test public void getAllUser(){ SqlSession session = MyBatisUtils.getSession(); UserMapper userMapper = session.getMapper(UserMapper.class); List<User> users = userMapper.getAll(); System.out.println(users); } @Test public void deleteBatchUser(){ SqlSession session = MyBatisUtils.getSession(); UserMapper userMapper = session.getMapper(UserMapper.class); List<Long> lists = new ArrayList<>(); lists.add(2L); lists.add(3L); userMapper.deleteBatch(lists); session.commit();//提交事务 session.close(); } @Test public void insertBatchUser(){ SqlSession session = MyBatisUtils.getSession(); UserMapper userMapper = session.getMapper(UserMapper.class); List<User> lists = new ArrayList<>(); lists.add(new User(null,"樊俊杰","456")); lists.add(new User(null,"樊太快了","789")); userMapper.insertBatch(lists); session.commit();//提交事务 session.close(); } }
2. MyBatis提供两种方式处理我们多表关联对象,嵌套查询和嵌套结果。
1.(多对一)关联映射。 (一个部门有多个用户,先从多个用户入手。)
1. User 和 Dept 实体类
public class User { private Long id; private String name; private String password; private Dept dept; } public class Dept { private Long id; private String name; }
2. DomainMapper.xml
<!-- 单向多对一嵌套结果(发一条左外连接sql解决问题,映射文件Mapper结果的手动封装ResultMap) 使用嵌套结果映射来处理重复的联合(association)结果的子集。 --> <select id="getUser1" resultMap="many2oneMap"> select u.id, u.name, u.password, d.id did, d.name dname from t_user u left join t_dept d on u.dept_id=d.id; </select> <!-- 封装查询的User结果 --> <resultMap type="com.gs.manytoone.User" id="many2oneMap"> <id column="id" property="id"/> <result column="name" property="name"/> <result column="password" property="password"/> <!--user的dept属性是一个对象Dept,使用联合查询关键字来封装 association:联合查询--> <association property="dept" javaType="com.gs.manytoone.Dept"> <id column="did" property="id"/> <result column="dname" property="name"/> </association> </resultMap> <!-- 单向多对一嵌套查询(先查询多方User,使用ResultMap来封装结果,在结果里,再通过User的dept属性,再查一次Dept对象.) --> <select id="getUser2" resultMap="many2oneList"> select id,name,password,dept_id from t_user; </select> <select id="getDeptById" parameterType="long" resultType="com.gs.manytoone.Dept"> select id,name from t_dept where id= #{id} </select> <resultMap type="com.gs.manytoone.User" id="many2oneList"> <id column="id" property="id"/> <result column="name" property="name"/> <result column="password" property="password"/> <association property="dept" column="dept_id" select="com.gs.manytoone.DomainMapper.getDeptById"> </association> </resultMap>
3. 测试类
@Test public void getUser1() { SqlSession session = MyBatisUtils.getSession(); List<User> users = session.selectList("com.gs.manytoone.DomainMapper.getUser1"); for (User user : users) { System.out.println(user); } session.commit(); session.close(); } @Test public void getUser2() { SqlSession session = MyBatisUtils.getSession(); List<User> users = session.selectList("com.gs.manytoone.DomainMapper.getUser2"); for (User user : users) { System.out.println(user); } session.commit(); session.close(); }
4.查询的结果:
User [id=1, name=张三, password=123456, dept=Department [id=1, name=财务部]]
User [id=2, name=李四, password=111, dept=Department [id=1, name=财务部]]
User [id=3, name=五五, password=123, dept=Department [id=2, name=后勤部]]
User [id=4, name=周六, password=122, dept=Department [id=3, name=经理部]]
User [id=5, name=周七, password=12332, dept=Department [id=3, name=经理部]]
2. (一对多,多对多)集合映射
员工和部门:
在部门方,需要查询到当前部门下的所有员工。----集合查询
1. User 和 Dept 实体类
public class User { private Long id; private String name; private String password; } public class Dept { private Long id; private String name; private Set<User> users = new HashSet<>(); }
2. DomainMapper.xml
<!-- 单向一对多嵌套结果(一条sql语句) --> <select id="getDept1" resultMap="one2manyMap"> select d.id did, d.name dname, u.id, u.name, u.password from t_dept d left join t_user u on d.id=u.dept_id order by d.id </select> <resultMap type="com.gs.onetomany.Dept" id="one2manyMap"> <id column="did" property="id"/> <result column="dname" property="name"/> <collection property="users" ofType="com.gs.onetomany.User"> <id column="id" property="id"/> <result column="name" property="name"/> <result column="password" property="password"/> </collection> </resultMap> <!-- 单向一对多嵌套查询(先查询多个Dept,使用ResultMap来封装结果,在结果里,再通过Dept的users属性,再查一次User对象.) --> <select id="getDept2" resultMap="one2manyList"> select d.id did , d.name dname from t_dept d </select> <select id="getUserById" parameterType="long" resultType="com.gs.onetomany.User"> select id,name,password from t_user where dept_id=#{?} </select> <resultMap type="com.gs.onetomany.Dept" id="one2manyList"> <id column="did" property="id"/> <result column="dname" property="name"/> <collection property="users" column="did" select="com.gs.onetomany.DomainMapper.getUserById"> </collection> </resultMap>
3. 测试类
@Test public void getUser1() { SqlSession session = MyBatisUtils.getSession(); List<Dept> allDept = session.selectList("com.gs.onetomany.DomainMapper.getDept1"); for (Dept dept : allDept) { System.out.println(dept); } session.commit(); session.close(); } @Test public void getUser2() { SqlSession session = MyBatisUtils.getSession(); List<Dept> allDept = session.selectList("com.gs.onetomany.DomainMapper.getDept2"); for (Dept dept : allDept) { System.out.println(dept); } session.commit(); session.close(); }
4.查询的结果:
Dept [id=1, name=财务部, users=[User [id=2, name=李四, password=111], User [id=1, name=张三, password=123456]]]
Dept [id=2, name=后勤部, users=[User [id=3, name=五五, password=123]]]
Dept [id=3, name=经理部, users=[User [id=5, name=周七, password=12332], User [id=4, name=周六, password=122]]]