对数据库的基本操作步骤 + 面试题
MyBatis 最初的设计是基于 XML 配置文件的,但随着 Java 的发展(Java 1.5 开始引入注解)和 MyBatis 自身的迭代升级,终于在 MyBatis 3 之后就开始支持基于注解的开发了。
下面我们使用 Spring Boot + MyBatis 注解的方式,来实现对数据库的基本操作,具体实现步骤如下。
MyBatis 注解版
1)创建数据表
1 2 3 4 5 6 7 8 9 10 drop table if exists `t_user`;create table `t_user` ( `id` bigint (20 ) not null auto_increment comment '主键id' , `username` varchar (32 ) default null comment '用户名' , `password` varchar (32 ) default null comment '密码' , `nick_name` varchar (32 ) default null , primary key (`id`) ) engine= innodb auto_increment= 1 default charset= utf8; 复制
2)添加依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > <version > 2.1.0</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 8.0.16</version > </dependency > 复制
3)增加配置文件
在 application.yml 文件中添加以下内容:
1 2 3 4 5 6 7 8 9 10 spring: datasource: url: jdbc:mysql://localhost:3306/learndb?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver mybatis: type-aliases-package: com.interview.model 复制
4)创建实体类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class UserEntity implements Serializable { private static final long serialVersionUID = -5980266333958177105L ; private Integer id; private String userName; private String passWord; private String nickName; public UserEntity (String userName, String passWord, String nickName) { this .userName = userName; this .passWord = passWord; this .nickName = nickName; } public Integer getId () { return id; } public void setId (Integer id) { this .id = id; } public String getUserName () { return userName; } public void setUserName (String userName) { this .userName = userName; } public String getPassWord () { return passWord; } public void setPassWord (String passWord) { this .passWord = passWord; } public String getNickName () { return nickName; } public void setNickName (String nickName) { this .nickName = nickName; } } 复制
5)增加 Mapper 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public interface UserMapper { @Select("select * from t_user") @Results({ @Result(property = "nickName", column = "nick_name") }) List<UserEntity> getAll () ; @Select("select * from t_user where id = #{id}") @Results({ @Result(property = "nickName", column = "nick_name") }) UserEntity getOne (Long id) ; @Insert("insert into t_user(username,password,nick_name) values(#{userName}, #{passWord}, #{nickName})") void insert (UserEntity user) ; @Update("update t_user set username=#{userName},nick_name=#{nickName} where id =#{id}") void update (UserEntity user) ; @Update({"<script> ", "update t_user ", "<set>", " <if test='userName != null'>userName=#{userName},</if>", " <if test='nickName != null'>nick_name=#{nickName},</if>", " </set> ", "where id=#{id} ", "</script>"}) void updateUserEntity (UserEntity user) ; @Delete("delete from t_user where id =#{id}") void delete (Long id) ; } 复制
使用 @Select、@Insert、@Update、@Delete、@Results、@Result 等注解来替代 XML 配置文件。
6)添加 Mapper 包扫描
在启动类中添加 @MapperScan,设置 Spring Boot 启动的时候会自动加载包路径下的 Mapper。
1 2 3 4 5 6 7 8 9 @SpringBootApplication @MapperScan("com.interview.mapper") public class MybatisApplication { public static void main (String[] args) { SpringApplication.run(MybatisApplication.class, args); } } 复制
7)编写测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 @RunWith(SpringRunner.class) @SpringBootTest public class MybatisApplicationTests { @Autowired private UserMapper userMapper; @Test public void testInsert () { userMapper.insert(new UserEntity("laowang" , "123456" , "老王" )); Assert.assertEquals(1 , userMapper.getAll().size()); } } 复制
相关面试题
1.MyBatis 有哪些优缺点?
答:MyBatis 优缺点如下:
优点:
相比于 JDBC 需要编写的代码更少
使用灵活,支持动态 SQL
提供映射标签,支持对象与数据库的字段关系映射
缺点:
SQL 语句依赖于数据库,数据库移植性差
SQL 语句编写工作量大,尤其在表、字段比较多的情况下
总体来说,MyBatis 是一个非常不错的持久层解决方案,它专注于 SQL 本身,非常灵活,适用于需求变化较多的互联网项目,也是当前国内主流的 ORM 框架。
2.以下不属于 MyBatis 优点的是?
A:可以灵活的编辑 SQL 语句
B:很好的支持不同数据库之间的迁移
C:能够很好的和 Spring 框架集成
D:提供映射标签支持对象和数据库的字段映射
答:B
题目解析:因为 MyBatis 需要自己编写 SQL 语句,但每个数据库的 SQL 语句有略有差异,所以 MyBatis 不能很好的支持不同数据库之间的迁移。
3.MyBatis 和 Hibernate 有哪些不同?
答:MyBatis 和 Hibernate 都是非常优秀的 ORM 框架,它们的区别如下:
灵活性:MyBatis 更加灵活,自己可以写 SQL 语句,使用起来比较方便;
可移植性:MyBatis 有很多自己写的 SQL,因为每个数据库的 SQL 可以不相同,所以可移植性比较差;
开发效率:Hibernate 对 SQL 语句做了封装,让开发者可以直接使用,因此开发效率更高;
学习和使用门槛:MyBatis 入门比较简单,使用门槛也更低。
4.“#”和“$
”有什么区别?
答:“#”是预编译处理,“$”是字符替换。 在使用“#”时,MyBatis 会将 SQL 中的参数替换成“?”,配合 PreparedStatement 的 set 方法赋值,这样可以有效的防止 SQL 注入,保证程序的运行安全。
5.在 MyBatis 中怎么解决实体类属性名和表字段名不一致的问题?
答:通常的解决方案有以下两种方式。
① 在 SQL 语句中重命名为实体类的属性名,可参考以下配置:
1 2 3 4 5 <select id ="selectorder" parametertype ="int" resultetype ="com.interview.order" > select order_id id, order_no orderno form order where order_id=#{id}; </select > 复制
② 通过 <resultMap>
映射对应关系,可参考以下配置:
1 2 3 4 5 6 7 8 9 10 11 <resultMap id ="BaseResultMap" type ="com.interview.mybatislearning.model.UserEntity" > <id column ="id" property ="id" jdbcType ="BIGINT" /> <result column ="username" property ="userName" jdbcType ="VARCHAR" /> <result column ="password" property ="passWord" jdbcType ="VARCHAR" /> <result column ="nick_name" property ="nickName" jdbcType ="VARCHAR" /> </resultMap > <select id ="getAll" resultMap ="BaseResultMap" > select * from t_user </select > 复制
6.在 MyBatis 中如何实现 like 查询?
答:可以在 Java 代码中添加 SQL 通配符来实现 like 查询,这样也可以有效的防治 SQL 注入,具体实现如下:
Java 代码:
1 2 3 4 String name = "%wang%" : List<User> list = mapper.likeName(name); 复制
Mapper 配置:
1 2 3 4 5 <select id ="likeName" > select * form t_user where name like #{name}; </select > 复制
7.MyBatis 有几种分页方式?
答:MyBatis 的分页方式有以下两种:
逻辑分页,使用 MyBatis 自带的 RowBounds 进行分页,它是一次性查询很多数据,然后在数据中再进行检索;
物理分页,自己手写 SQL 分页或使用分页插件 PageHelper,去数据库查询指定条数的分页数据形式。
8.RowBounds 是一次性查询全部结果吗?为什么?
答:RowBounds 表面是在“所有”数据中检索数据,其实并非是一次性查询出所有数据。因为 MyBatis 是对 JDBC 的封装,在 JDBC 驱动中有一个 Fetch Size 的配置,它规定了每次最多从数据库查询多少条数据,假如你要查询更多数据,它会在执行 next() 的时候,去查询更多的数据。 就好比你去自动取款机取 10000 元,但取款机每次最多能取 2500 元,要取 4 次才能把钱取完。对于 JDBC 来说也是一样,这样做的好处是可以有效的防止内存溢出。
9.为什么阿里巴巴不允许使用 HashMap 或 Hashtable 作为查询结果集直接输出?
答:因为使用 HashMap 或 Hashtable 作为查询结果集直接输出,会导致值类型不可控,给调用人员造成困扰,给系统带来更多不稳定的因素。
10.什么是动态 SQL?
答:动态 SQL 是指可以根据不同的参数信息来动态拼接的不确定的 SQL 叫做动态 SQL,MyBatis 动态 SQL 的主要元素有:if、choose/when/otherwise、trim、where、set、foreach 等。 以 if 标签的使用为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <select id="findUser" parameterType="com.interview.entity.User" resultType="com.interview.entity.User" > select * from t_user where <if test="id!=null" > id = #{id} </if> <if test="username!=null" > and username = #{username} </if> <if test="password!=null" > and password = #{password} </if> </select> 复制
11.为什么不建议在程序中滥用事务?
答:因为事务的滥用会影响数据的 QPS(每秒查询率),另外使用事务的地方还要考虑各方面回滚的方案,如缓存回滚、搜索引擎回滚、消息补偿、统计修正等。
12.如何开启 MyBatis 的延迟加载?
答:只需要在 mybatis-config.xml 设置 <setting name="lazyLoadingEnabled" value="true"/>
即可打开延迟缓存功能,完整配置文件如下:
1 2 3 4 5 6 7 8 <configuration > <settings > <setting name ="lazyLoadingEnabled" value ="true" /> </settings > </configuration > 复制
13.什么是 MyBatis 的一级缓存和二级缓存?
答:MyBatis 缓存如下:
一级缓存是 SqlSession 级别的,是 MyBatis 自带的缓存功能,并且无法关闭,因此当有两个 SqlSession 访问相同的 SQL 时,一级缓存也不会生效,需要查询两次数据库;
二级缓存是 Mapper 级别的,只要是同一个 Mapper,无论使用多少个 SqlSession 来操作,数据都是共享的,多个不同的 SqlSession 可以共用二级缓存,MyBatis 二级缓存默认是关闭的,需要使用时可手动开启,二级缓存也可以使用第三方的缓存,比如,使用 Ehcache 作为二级缓存。
手动开启二级缓存,配置如下:
1 2 3 4 5 6 7 8 <configuration > <settings > <setting name ="cacheEnabled" value ="true" /> </settings > </configuration > 复制
14.如何设置 Ehcache 为 MyBatis 的二级缓存?
答:可直接在 XML 中配置开启 EhcacheCache,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <mapper namespace ="com.interview.repository.ClassesReposirory" > <cache type ="org.mybatis.caches.ehcache.EhcacheCache" > <property name ="timeToIdleSeconds" value ="3600" /> <property name ="timeToLiveSeconds" value ="3600" /> <property name ="memoryStoreEvictionPolicy" value ="LRU" /> </cache > <select id ="findById" parameterType ="java.lang.Long" resultType ="com.interview.entity.Classes" > select * from classes where id = #{id} </select > </mapper > 复制
15.MyBatis 有哪些拦截器?如何实现拦截功能?
答:MyBatis 提供的连接器有以下 4 种。
Executor:拦截内部执行器,它负责调用 StatementHandler 操作数据库,并把结果集通过 ResultSetHandler 进行自动映射,另外它还处理了二级缓存的操作。
StatementHandler:拦截 SQL 语法构建的处理,它是 MyBatis 直接和数据库执行 SQL 脚本的对象,另外它也实现了 MyBatis 的一级缓存。
ParameterHandler:拦截参数的处理。
ResultSetHandler:拦截结果集的处理。
拦截功能具体实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})}) public class TestInterceptor implements Interceptor { public Object intercept (Invocation invocation) throws Throwable { Object target = invocation.getTarget(); Method method = invocation.getMethod(); Object[] args = invocation.getArgs(); Object result = invocation.proceed(); return result; } public Object plugin (Object target) { return Plugin.wrap(target, this ); } } 复制
总结
通过本文可以看出 MyBatis 注解版和 XML 版的主要区别是 Mapper 中的代码,注解版把之前在 XML 的 SQL 实现,全部都提到 Mapper 中了,这样就省去了配置 XML 的麻烦。