半自动化的ORM框架,相比于全自动化的Hibernate,Mybatis的封装程度没有它那么高,但主要解决了SQL和对象的映射关系。
在Mybatis里面,SQL和代码是分离的。
Mybatis是⼀个半ORM(对象关系映射)框架,它内部封装了 JDBC,开发时只需要关注 SQL 语句本身,不需要花费精⼒去处理加载驱动、创建连接、创建 Statement 等繁杂的过程。程序员直接编写原⽣态 SQL,可以严格控制 SQL 执⾏性能,灵活度⾼。
MyBatis 可以使⽤ XML 或注解来配置和映射原⽣信息,将 POJO 映射成数据库中的记录,避免了⼏乎所有的JDBC 代码和⼿动设置参数以及获取结果集。
通过 XML ⽂件或注解的⽅式将要执⾏的各种 Statement 配置起来,并通过 Java 对象和 Statement 中 SQL 的动态参数进⾏映射⽣成最终执⾏的 SQL 语句,最后由 MyBatis 框架执⾏ SQL并将结果映射为 Java 对象并返回。(从执⾏ SQL到返回 Result 的过程)。
总结:
mybatis的优点其实也是缺点,正因为mybatis的使用简单,数据的可靠性、完整性的瓶颈更多依赖与程序员对sql的使用水平。sql写在xml里。 虽然方便了修改、优化和统一预览,但可读性很低,测试也非常困难而且受限,无法向JDBC那样在代码里根据逻辑实现复杂动态sql拼接。mybatis简单看就是提供了字段映射和对象关系的jdbc,省去了数据赋值到对象的步骤而已。除此之外并无太多作为,不要把它想象成hibernate那样强大,简单小巧易用上手,方便浏览修改sql就是它最大的优点了。
hibernate
完全可以通过对象关系模型实现对数据库的操作,拥有完整的 JavaBean 对象与数据库的映射结构来自动生成 sql。而 mybatis
仅有基本的字段映射,对象数据以及对象实际关系仍然需要通过手写 sql 来实现和管理。
hibernate 通过它强大的映射结构和 hql 语言,大大降低了对象与数据库(oracle、mysql 等)的耦合性,而 mybatis 由于需要手写 sql,因此与数据库的耦合性直接取决于程序员写 sql 的方法,如果 sql 不具通用性而用了很多某数据库特性的 sql 语句的话,移植性也会随之降低很多,成本很高。
hibernate 日志系统非常健全,涉及广泛,包括:sql 记录、关系异常、优化警告、缓存提示、脏数据警告等;而 mybatis 则除了基本记录功能外,功能薄弱很多。
hibernate 配置要比 mybatis 复杂的多,学习成本也比 mybatis 高。但也正因为 mybatis 使用简单,才导致它要比 hibernate 关心很多技术细节。mybatis 由于不用考虑很多细节,开发模式上与传统 jdbc 区别很小,因此很容易上手并开发项目,但忽略细节会导致项目前期 bug 较多,因而开发出相对稳定的软件很慢,而开发出软件却很快。hibernate 则正好与之相反。但是如果使用 hibernate 很熟练的话,实际上开发效率丝毫不差于甚至超越 mybatis。
由于 mybatis 的 sql 都是写在 xml 里,因此优化 sql 比 hibernate 方便很多。而 hibernate 的 sql 很多都是自动生成的,无法直接维护 sql;虽有 hql,但功能还是不及 sql 强大,见到报表等变态需求时,hql 也歇菜,也就是说 hql 是有局限的;hibernate 虽然也支持原生 sql,但开发模式上却与 orm 不同,需要转换思维,因此使用上不是非常方便。总之写 sql 的灵活度上 hibernate 不及 mybatis。
首先,十年前我们主要使用的ORM框架就是iBatis,而阿里巴巴是对国内Java开发者影响最大的一家公司。阿里在国内Java社区的影响力有目共睹,这个大家应该都能感受到, 阿里对Java社区贡献了很多实用的开源工具,并且国内Java开发者对于阿里开源的产品接纳程度也最高。
MyBatis封装较少,提供的切入点较多,适合进行架构。遇到超级复杂的场景的时候有不错的sql支持。曾经JPA适合做增删改,mybatis只擅长查询,但是现在的tk.mybatis已经补上了这一块短板,而JPA的依然没有补上他的查询短板。在复杂情况下需要在代码里嵌入大量sql片段或手动用代码拼装sql,但是老实说,都到这份上了,写sql不是还更快一点?因此,做企业级应用时,如果组内Hibernate会的人多,可以考虑用这个,但是依然会埋下一个性能的坑。做互联网级应用时,建议还是用Mybatis吧。
综合考虑,Mybatis的优点是简单高效,优化起来也方便,比较符合现在的开发节奏,现在的互联网公司都是先快速开发占领市场,然后再优化代码。而且这个过程需求经常是变来变去的,开发人员也有流动性,这种情况下用Mybatis显然更加适合。
动态 sql 是 mybatis 的主要特性之一,在 mapper 中定义的参数传到 xml 中之后,在查询之前, mybatis 会对其进行动态解析。mybatis 为我们提供了两种支持动态 sql 的语法:#{}
以及${}
#{}:参数占位符,即预编译
${}:字符串替换符,即SQL拼接
#{}:很大程度上能防止sql 注入
${}:不能防止sql 注入
#{}:变量替换是在DBMS 中
${}:变量替换是在 DBMS 外
DBMS:数据库管理系统(Database Management System)是一种操纵和管理数据库的大型软件,是用于建立、使用和维护数据库,简称DBMS。它对数据库进行统一的管理和控制,以保证数据库的安全性和完整性。
用户通过DBMS访问数据库中的数据,数据库管理员也通过DBMS进行数据库的维护工作。它提供多种功能,可使多个应用程序和用户用不同的方法在同时或不同时刻去建立,修改和询问数据库。
#{}:将传入的数据都当成一个字符串,会对传入的变量自动加一个单引号。如:user_id = #{userId}
,如果传入的值是111,那么解析成sql时的值为user_id = ‘111’,如果传入的值是id,则解析成的sql为user_id = ‘id’
。
${}:将传入的参数直接显示生成在sql中,且不加任何引号。如:user_id = ${userId}
,如果传入的值是111,那么解析成sql时的值为user_id = 111 , 如果传入的值是id,则解析成的sql为user_id = id
。
#{}:动态解析 -> 预编译 -> 执行
${}:动态解析 -> 编译 -> 执行
((page -1)*pagesize)
和每页几条数据(pagesize),调用mapper方法讲RowBounds对象传递进去,RowBounds中有2个字段offset和limit。这种方式获取所有的ResultSet,从ResultSet中的offset位置开始获取limit个记录。但这并不意味着JDBC驱
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelperartifactId>
<version>5.3.0version>
dependency>
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelper-spring-boot-autoconfigureartifactId>
<version>1.4.1version>
dependency>
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelper-spring-boot-starterartifactId>
<version>1.4.1version>
dependency>
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/emp_dep?useUnicode=true&characterEncoding=UTF-8&useSSL=true&serverTimezone=UTC
username: root
password: root
pagehelper:
#指定数据库的方言
helper-dialect: mysql
#分页合理化参数,默认值为false。当该参数设置为 true 时,
#pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页。
reasonable: true
#支持通过 Mapper 接口参数来传递分页参数,默认值false,
#分页插件会从查询方法的参数值中,自动根据上面 params 配置的字段中取值,查找到合适的值时就会自动分页
support-methods-arguments: true
#为了支持startPage(Object params)方法,增加了该参数来配置参数映射,用于从对象中根据属性名取值
params: =count=countSql
Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页。可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
延迟加载又称为懒加载,mybatis是支持懒加载的。在进行关联查询时,按照设置的延迟规则推迟对关联对象的查询。延迟加载可以有效的减少数据库的压力。延迟加载只是针对有延迟设置的关联对象的推迟查询,对于主主查询是直接进行执行SQL语句。
MyBatis关联查询加载时机:
原理:延迟加载的基本原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。
立即加载方式:查询账户(Account)信息并且关联查询用户(User)信息。
select * from account a left outer join user u on u.id = a.uid
延迟加载方式:如果先查询账户(Account)信息即可满足要求。当我们需要查询用户(User)信息时再查询用户(User)信息。把对用户(User)信息的按需去查询就是延迟加载。
SQL语句:
select * from account
封装到Account类里,
当使用到 Account类 的成员变量 User类时,执行
SQL语句:
select * from account where uid = #{uid}
@Select
@Delete
@Insert
@Update
@Options能够设置缓存时间,能够为对象生成自增的key。
上面的这些注解中的每一个代表了执行的真实SQL。它们每一个都使用字符串数组(或单独的字符串)。如果传递的是字符串数组,它们由每个分隔它们的单独空间串联起来。
@Mapper:
作用:在接口类上添加了@Mapper,在编译之后会生成相应的接口实现类
添加位置:接口类上面
@Mapper
public interface UserDAO {
//代码
}
@MapperScan:
作用:指定要变成实现类的接口所在的包,然后包下面的所有接口在编译之后都会生成相应的实现类
添加位置:是在Springboot启动类上面添加
@SpringBootApplication
@MapperScan("com.winter.dao")
public class SpringbootMybatisDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootMybatisDemoApplication.class, args);
}
缓存其实就是存储在内存中的临时数据,这里的数据量会比较小,一般来说,服务器的内存也是有限的,不可能将所有的数据都放到服务器的内存里面,所以, 只会把关键数据放到缓存中,缓存因为速度快,使用方便而出名!
BS架构里面,用户的所有操作都是对数据库的增删改查,其中查询的操作是最多的,但如果用户想要某个数据时每次都去数据库查询,这无疑会增加数据库的压力,而且获取时间效率也会降低,所以为了解决这些问题,缓存应用而生,使用了缓存之后,服务器只需要查询一次数据库,然后将数据保存到服务器主机的内存中,以后读取时就直接取内存中的数据,而不需要每次都查数据库,这种方案除了降低数据库压力之外,还提高了响应速度。
通常情况下,都会将那些变化较少且经常用到的数据会放到缓存中,比如像字典、系统参数、有固定值的状态码等等;另外将用户保存到缓存也是一种很好的策略,这样登录的时候就可以极速响应了。
myabtis的缓存分为两类,一级缓存和二级缓存。一级缓存是默认开启的,它在一个sqlSession
会话里面的所有查询操作都会保存到缓存中。一般来说一个请求中的所有增删改查操作都是在同一个sqlSession里面的,所以我们可以认为每个请求都有自己的一级缓存。如果同一个sqlSession会话中2个查询中间有一个 insert 、update或delete 语句,那么之前查询的所有缓存都会清空,防止脏读。
Reader reader = Resources.getResourceAsReader("config/configuration.xml");
//创建数据工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = builder.build(reader);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
// 。。。。。。
// 这中间所走的所有查询操作都会进行缓存,一旦关闭sqlSession会话,缓存则会刷新
//释放会话
sqlSession.clearCache();
// 关闭会话
sqlSession.close();
二级缓存是全局的。也就是说:多个请求可以共用一个缓存,二级缓存需要手动开启,有2种方式配置二级缓存。
配置方式:在mybatis.xml文件里面配置
<settings>
settings>
public static void main(String[] args) throws IOException {
// 加载mybatis配置文件
Reader reader = Resources.getResourceAsReader("config/configuration.xml");
//创建数据工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = builder.build(reader);
// 第一个会话
SqlSession sqlSession = sqlSessionFactory.openSession(true);
// 获取会话一的mapper接口对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 第一次查询
User user = mapper.selectByPrimaryKey("3rfrf34r34");
//释放第一个会话
sqlSession.clearCache();
sqlSession.close();
// 第二个会话
SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
// 获取会话二的mapper接口对象
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
// 第二次查询
User user1 = mapper2.selectByPrimaryKey("3rfrf34r34");
// 释放第二个会话
sqlSession2.clearCache();
sqlSession2.close();
}
注意事项:
(1)一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。
(2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置 ;
(3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear 掉并重新更新,如果开启了二级缓存,则只根据配置判断是否刷新。
Mybatis动态sql可以在Xml映射文件内,以标签的形式编写动态sql,执行原理是根据表达式的值 完成逻辑判断 并动态拼接sql的功能。Mybatis提供了9种动态sql标签:trim | where | set | foreach | if | choose | when | otherwise | bind。
在Java代码中添加sql通配符。
string wildcardname = “%smi%”;
list<name> names = mapper.selectlike(wildcardname);
<select id=”selectlike”>
select * from foo where bar like #{value}
select>
在sql语句中拼接通配符,会引起sql注入
string wildcardname = “smi”;
list<name> names = mapper.selectlike(wildcardname);
<select id=”selectlike”>
select * from foo where bar like "%"${value}"%"
select>
properties标签就是从外部引入其他配置文件 ,resource属性是从本地引入,url属性是从网络下载引入
<properties resource="jdbcconfig.properties">properties>
settings标签包含很多重要的设置setting标签,name就是设置项的名称,value就是设置项的值
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
settings>
setting还有很多设置项,具体的可以去Mybatis中文手册查看
typeAliases起别名的标签,为全限定类名起别名,书写简便,注意别名不区分大小写
有属性default,和子标签environment,default属性指定使用哪种环境,值为environment标签的id值,transactionManager
的type属性事务管理器的类别,使用JDBC即可。dataSource的type属性POOLED值,是指使用连接池技术,使用POOLED。
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
dataSource>
environment>
environments>
其子标签mapper就是加载映射文件的
resource属性,是加载类路径下的映射文件
class属性,是加载指定的接口,一般为实体类对应接口
package标签,加载指定包下的所有类,类必须和其对应的映射文件放在一起,否则会加载失败
<mappers>
<mapper resource="EmployeeMapper.xml" />
<mapper class="org.mybatis.builder.PostMapper"/>
<package name="org.mybatis.builder"/>
mappers>
(1)第一种:
//DAO层的函数
Public UserselectUser(String name,String area);
//对应的xml,#{0}代表接收的是dao层中的第一个参数,#{1}代表dao层中第二参数,更多参数一致往后加即可。
<select id="selectUser"resultMap="BaseResultMap">
select * fromuser_user_t whereuser_name = #{0} anduser_area=#{1}
</select>
(2)第二种: 使用 @param 注解:
public interface usermapper {
user selectuser(@param(“username”) string username,@param(“hashedpassword”) string hashedpassword);
}
然后,就可以在xml像下面这样使用(推荐封装为一个map,作为单个参数传递给mapper):
<select id=”selectuser” resulttype=”user”>
select id, username, hashedpassword
from some_table
where username = #{username}
and hashedpassword = #{hashedpassword}
</select>
(3)第三种:多个参数封装成map
try{
//映射文件的命名空间.SQL片段的ID,就可以调用对应的映射文件中的SQL
//由于我们的参数超过了两个,而方法中只有一个Object参数收集,因此我们使用Map集合来装载我们的参数
Map<String, Object> map = new HashMap();
map.put("start", start);
map.put("end", end);
return sqlSession.selectList("StudentID.pagination", map);
}catch(Exception e){
e.printStackTrace();
sqlSession.rollback();
throw e; }
finally{
MybatisUtil.closeSqlSession();
}