概念/作用:
持久层框架,通过xml或注解的方式将要执行的各种 statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句。最后mybatis框架执行sql并将结果映射为java对象并返回。采用ORM思想解决了实体和数据库映射的问题,对jdbc 进行了封装,屏蔽了jdbc api 底层访问细节,使我们不用与jdbc api 打交道,就可以完成对数据库的持久化操作。
数据源环境environment可以配置多个
mapper文件里面配置了多个select/insert/update/delete sql语句,取用的时候是采用标识:namespace+id
默认事务不提交
Mybatis----多表操作
一对一查询,一个用户只有一个订单
对应的sql语句:select * from orders o,user u where o.uid=u.id;
user表和orders表连接,自然就需要user+order两个对象来映射返回的所有列
<mapper namespace="com.mapper.OrderMapper">
<resultMap id="orderMap" type="com.domain.Order">
<!--手动指定字段与实体属性的映射关系 column: 数据表的字段名称 property:实体的属性名称 -->
<id column="oid" property="id"></id>
<result column="ordertime" property="ordertime"></result>
<result column="total" property="total"></result>
//主键使用id标签
<id column="id" property="id"></id>
<result column="uid" property="user.id"></result>
<result column="username" property="user.username"></result>
<result column="password" property="user.password"></result>
<result column="birthday" property="user.birthday"></result>
</resultMap>
<select id="findAll" resultMap="orderMap">
select * from orders o,user u where o.uid=u.id
</select>
</mapper>
注解配置
首先核心配置文件中:
<mappers>
<!--扫描使用注解的类所在的包-->
<package name="com.mapper"></package>
</mappers>
在接口中进行sql语句的映射配置
public interface UserMapper {
@Insert("insert into user values(#{id},#{username},#{password},#{birthday})")
public void save(User user);
@Update("update user set username=#{username},password=#{password} where id=#{id}")
public void update(User user);
@Delete("delete from user where id=#{id}")
public void delete(int id);
@Select("select * from user where id=#{id}")
public User findById(int id);
@Select("select * from user")
public List<User> findAll();
@Select("select * from user")
@Results({
@Result(id=true ,column = "id",property = "id"),
@Result(column = "username",property = "username"),
@Result(column = "password",property = "password"),
@Result(
property = "orderList",
column = "id",
javaType = List.class,
many = @Many(select = "com.mapper.OrderMapper.findByUid")
)
})
public List<User> findUserAndOrderAll();
上面等价与
先在user表中查出uid 然后 在订单表中查询 select * from order where uid=user.uid
@Select("SELECT * FROM USER")
@Results({
@Result(id = true,column = "id",property = "id"),
@Result(column = "username",property = "username"),
@Result(column = "password",property = "password"),
@Result(
property = "roleList",
column = "id",
javaType = List.class,
many = @Many(select = "com.itheima.mapper.RoleMapper.findByUid")
)
})
public List<User> findUserAndRoleAll();
}
mybatis常见面试题:
具体问题
1、#{}和${}区别
都属于占位符
#{}是预编译处理 使用prepareStatedment的参数设置方法;$ {}是字符串替换,参数传递
#{} :mybatis内部会创建配prepareStatedment 使用#{}格式的语法在mybatis中使用prepareStatedment语句来安全的设置值; MyBatis在处理#{}时,会将SQL中的#{}替换为?号,使用PreparedStatement的set方法来赋值;MyBatis在处理 $ { } 时,就是把 ${ } 替换成变量的值。
${}是 Properties 文件中的变量占位符,它可以用于标签属性值和 sql 内部,属于静态文本替换,比如${driver}会被静态替换为com.mysql.jdbc.Driver。
#{}是 sql 的参数占位符,MyBatis 会将 sql 中的#{}替换为?号,在 sql 执行前会使用 PreparedStatement 的参数设置方法,按序给 sql 的?号占位符设置参数值,比如 ps.setInt(0, parameterValue),#{item.name} 的取值方式为使用反射从参数对象中获取 item 对象的 name 属性值,相当于 param.getItem().getName()。
预编译是:指的是数据库驱动在发送 sql 语句和参数给 DBMS 之前对 sql 语句进行编译,这样 DBMS 执行 sql 时,就不需要重新编译。
2、mapper.xml 标签有哪些?resultMap和resultType的区别?
常用的<select> <delete> <insert> <update>,除此之外:
<resultMap>、<parameterMap>、<sql>、<include>、<selectKey>,加上动态 sql 的 9 个标签,trim|where|set|foreach|if|choose|when|otherwise|bind等,其中为 sql 片段标签,通过<include>标签引入 sql 片段,<selectKey>为不支持自增的主键生成策略标签。
resultMap 是一种"查询结果集---Bean对象”属性名称映射关系,列和bean对象的属性的映射,一般适用于多表连接
Resulttype---一般适用于pojo(简单对象)类型数据,简单的单表查询
如果配置成类,一般映射会遵循约定:要求Bean对象字段名和查询结果集的属性名相同(可以大小写不同,大小写不敏感)。因为这个自动映射,可以省略调resultMap进行属性名映射。也可以是int long这种pojo类型
3.dao接口的底层原理(如何返回实现类并且封装好结果集合的)?
通过动态代理实现接口的实现类,在mapperPorxy(实现invocationHandler接口)中的invoke中调用sql语句:mapperMethod.execute(sqlSession, args);
===》会进行paramname解析 比如将#{}解析为?
4、MyBatis 是否可以映射 Enum 枚举类?
答:MyBatis 可以映射枚举类,不单可以映射枚举类,MyBatis 可以映射任何对象到表的一列上。映射方式为自定义一个 TypeHandler,实现 TypeHandler 的 setParameter()和 getResult()接口方法。TypeHandler 有两个作用,一是完成从 javaType 至 jdbcType 的转换,二是完成 jdbcType 至 javaType 的转换,体现为 setParameter()和 getResult()两个方法,分别代表设置 sql 问号占位符参数和获取列查询结果。
实现自定义的typeHandler: 继承BaseTypeHandler<T> ---》 Mybatis就是依赖泛型参数<T>,获得泛型参数Class对象,再与反射获得的bean属性Class,进行一一对应的
除了上面的“智能”绑定外,我们还可以手动绑定TypeHandler。
<result property="phone" column="phone" typeHandler="com.mybatis3.typehandlers.PhoneTypeHandler"/>
8、typehandler
mybatis默认提供的typehandler:
比如:
public TypeHandlerRegistry() {
register(Boolean.class, new BooleanTypeHandler());
register(boolean.class, new BooleanTypeHandler());
register(JdbcType.BOOLEAN, new BooleanTypeHandler());
register(JdbcType.BIT, new BooleanTypeHandler());
register(JdbcType.VARCHAR, new StringTypeHandler());
自定义注册:
register(typeReference.getRawType(), typeHandler);
.getRawType()就是返回 泛化类型<T>
9、mybatis可以分页吗?如何?
Mybatis可以通过传递RowBounds对象,来进行数据库数据的分页操作,然而遗憾的是,该分页操作是对ResultSet结果集进行分页,也就是人们常说的逻辑分页,而非物理分页。
Rowbounds两个属性:offset + limit
offset就是从哪里开始读,最多读limit行
实现:
跳转到offset位置:
for (int i = 0; i < rowBounds.getOffset(); i++) {
rs.next();
}
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
skipRows(rsw.getResultSet(), rowBounds);
while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
Object rowValue = getRowValue(rsw, discriminatedResultMap);
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
}
}
10、mybatis如何返回在执行insert时生成的自增主键?
在Mybatis中,执行insert操作时,如果我们希望返回数据库生成的自增主键值,那么就需要使用到KeyGenerator对象。
。Mybatis是对JDBC的封装,其Jdbc3KeyGenerator类,就是使用下面的原理,来返回数据库生成的主键值的。
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "root", "123");
conn.setAutoCommit(false);
PreparedStatement pstm = conn.prepareStatement("insert into students(name, email) values(?, ?)",
Statement.RETURN_GENERATED_KEYS);
pstm.setString(1, "name1");
pstm.setString(2, "email1");
pstm.addBatch();
pstm.setString(1, "name2");
pstm.setString(2, "email2");
pstm.addBatch();
pstm.executeBatch();
// 返回自增主键值
ResultSet rs = pstm.getGeneratedKeys();
while (rs.next()) {
Object value = rs.getObject(1);
System.out.println(value);
}
conn.commit();
rs.close();
pstm.close();
conn.close();
output:
246
247
Mapper.Xml配置方式。
<insert id="insertStudents" useGeneratedKeys="true" keyProperty="studId" parameterType="Student">
主键赋值到keyproperty
11、如何批量插入List<student>?
<insert id="insertStudents" useGeneratedKeys="true" keyProperty="studId" parameterType="java.util.ArrayList">
INSERT INTO
STUDENTS(STUD_ID, NAME, EMAIL, DOB, PHONE)
VALUES
<foreach collection="list" item="item" index="index" separator=",">
(#{item.studId},#{item.name},#{item.email},#{item.dob}, #{item.phone})
</foreach>
</insert>
此时返回主键id列表为null
11、Mybatis之foreach批量insert,返回主键id列表(修复Mybatis返回null的bug)
使用simpleExectuor/reuse可以返回
使用batch返回的是null
因为batch:如果传递的是List<Student>,那么,将包装为一个Map<String, Collection>对象
SimpleExecutor和ReuseExecutor可以正确返回foreach批量插入后的id列表的原理:
getParameters()方法,会再次处理参数类型,拆封map,获取map中的vaule 即collection对象
BatchResult又把Map<String, List<Student>>放到List中,于是,参数对象数据结构就变成了List<Map<String, List<Student>>>。
Java接口是否继承Object类?
看似是继承的,因为一个接口可以直接调用hashcode toString方法 可以通过编译
但是如果接口继承了object类 接口就不能实例化
所以是,虚拟机制造了“继承的假象”。虚拟机会在顶层接口(没有父接口的接口)中自动定义一系列对应于Object类中“public”方法的虚方法,除非已经得到了显式定义,这点跟虚拟机会自动定义一个默认构造器类似。