MyBatis
简介:
1.本是apache的一个开源项目iBatis,2010年这个项目迁移到了google code ,改名MyBatis,2013年迁移到Github;
2.MyBatis是一款优秀的持久层框架,支持定制化SQL,存储过程以及高级映射。
几乎避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。
3.MyBatis可以使用简单的xml或注解来配置和映射原生类型,接口和java的POJO(Plain Old Java Objects)为数据库中的记录。
如何获得Mybatis?
maven:<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --><dependency>
<groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</version>
</dependency>
github:Release mybatis-3.5.7 · mybatis/mybatis-3 · GitHub
持久化?
数据持久化,将程序的数据在持久状态和瞬时状态转化的过程。
内存:断电即失
数据库(jdbc),io文件持久化,可让数据持久化。
生活:冷藏,罐头。
为什么需要持久化?
有一些对象,不能让他丢掉。内存太贵了。
持久层
Dao层,Service层,Controller层..
完成持久化工作的代码块,叫做持久层
层界限明显
为什么需要Mybatis ?
帮助程序员将数据存入到数据库中;方便 ;穿透jdbc代码太复杂了 ; 简化,框架;自动化;sql与代码分离,提高可维护性 ;提供映射标签,支持对象与数据库的orm字段关系映射。提供对象关系映射标签,支持对象关系组件维护。提供xml标签,支持编写动态sql
Mybatis程序
搭建环境-->导入Mybatis-->编写代码-->测试!
搭建数据库:
新建项目:
新建一个普通maven项目
删除src目录,当一个父工程,然后导入maven依赖,
创建模块
编写mybatis核心配置文件
每 一 个 MyBatis 的 应 用 程 序 都 以 一 个 SqlSessionFactory 对 象 的 实 例 为 核 心 。 SqlSessionFactory 对 象 的 实 例 可 以 通 过 SqlSessionFactoryBuilder(建造者模式) 对 象 来 获 得 。 SqlSessionFactoryBuilder 对象可以从 XML 配置文件,或从 Configuration 类的习惯准备的实例中构建 SqlSessionFactory 对象。
SqlSession 对象完全包含以数据库为背景的所有执行 SQL 操作的方法
编写mybatis工具类
//工具类,获取 sqlSessionFactory 工厂模式 public class MybatisUtils { //提升作用域 private static SqlSessionFactory sqlSessionFactory; static { try { //获取sqlSessionFactory对象 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); }catch (IOException e){ e.printStackTrace(); } } //我们可以获得 SqlSession 的实例了,SqlSession 对象完全包含以数据库为背景的所有执行SQL操作的方法。 // 以用 SqlSession 实例来直接执行已映射的SQL语句 public static SqlSession getSqlSession(){ //sqlSessionFactory调用open方法返回一个sqlsession return sqlSessionFactory.openSession(); } }
编写代码
实体类 Dao接口 接口实现类
Dao 接口
public interface UserMapper { List<User> getUserList(); }
接口实现类 由以前的UserMapperIml转换为Mapper配置文件
注意点:
MapperRegistry: 核心注册文件中注册mapper
测试:
CRUD
插入数据一定需要提交事务
如果不提交,数据库其实未更新,必须有 sqlSession.commit() 才会插入成功
<!--namespace 绑定也给对象的DAO/MAPPER接口--> <mapper namespace="com.xie.dao.UserMapper"> <!--查询 id为重写的方法的名字 namespace中的方法名 resultType是sql结果类型 返回值 parameterType 参数类型 --> <select id="getUserList" resultType="com.xie.pojo.User" > select * from mybatis.user </select> <select id="getUserById" parameterType="int" resultType="com.xie.pojo.User"> select * from mybatis.user where id=#{id} </select> <!--对象中的属性 可以直接取出来--> <insert id="addUser" parameterType="com.xie.pojo.User"> insert into mybatis.user(id,name,pwd) values(#{id},#{name},#{pwd}); </insert> <update id="updateUser" parameterType="com.xie.pojo.User"> update mybatis.user set name = #{name},pwd=#{pwd} where id=#{id}; </update> <delete id="deleteUser" parameterType="int"> delete from mybatis.user where id=#{id}; </delete> </mapper>
主意: 增删改 需要提交事务!!
配置文件的resource必须是“/” 不能是 “ .”
基本类型的参数或者String 类型,需要加上
引用类型不需要加
如果只有一个基本类型,可以忽略,建议加上
我们在sql中引用的就是我们这里的@Param("")中设定的属性名
#{ } 是预编译的 ${}
如果实体类,或者数据库的表,字段或者参数过多,我们应当考虑使用Map ,用map代替传对象,或者用注解;直接在sql取出key即可。对象传递参数,直接在sql中取对象的属性。只有一个基本类型参数的情况下,可以直接在sql中取到
<!--传递的是map的键--> <insert id="addUser2" parameterType="map"> insert into mybatis.user (id, name, pwd) values (#{userid}, #{userName}, #{passWord}) </insert>
//用map传 @Test public void addUser2(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); Map map = new HashMap<String,Object>(); map.put("userid",2); map.put("userName","tom"); map.put("passWord","456789"); int res = mapper.addUser2(map); if(res>0){ System.out.println("插入成功"); } //提交事务 sqlSession.commit(); //关闭资源 sqlSession.close(); }
模糊查询
1.java 代码执行的时候,可以传递通配符%%,为了防止注入,一般只让用户传递值进来
2.在sql拼接中使用通配符固定好 !
配置解析
1.核心配置文件
mybatis-config.xml
MyBatis的配置文件包含了会深深影响MyBatis行为的设置和属性信息。
configuration (配置)properties(属性) settings(设置)typeAliases(类型别名)typeHandlers(类型处理器)objectFactory(对象工厂)plugins(插件)environments(环境配置)environment(环境变量)transactionManager(事务管理器)dataSource(数据源)databaseIdProvider(数据库厂商标识)mappers(映射器)
2.environments(环境配置)
尽管可以配置多个环境,但每个SqlSessionFactory实例只能选择一种环境
事务管理器transactionManager有两种 JDBC MANAGED,默认使用JDBC
dataSource数据源:**默认的POOLED**
3.属性(properties)
我们可以通过properties属性来实现引用配置文件;
这些属性都是可以外部配置且可动态替换的,既可以在典型的java属性文件中配置,亦可通过properties元素的子元素来传递。【db.properties】
driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8 username=root password=root
在核心配置文件中进行引入此配置文件
可以直接引入外部文件,增加一些属性配置,如果两个文件有同一个字段,优先使用外部配置文件的配置!
类型别名 typeAliases
类型别名是为 Java 类型命名一个短的名字。
存在的意义仅在于用来减少类完全限定名的冗余。
<!--实体类起别名--> <typeAliases> <typeAlias type="com.xie.pojo.User" alias="User"/> </typeAliases>
也可以指定一个包名,MyBatis会在包名下面搜索需要的Java Bean 比如:扫描实体类的包,它的默认别名就为这个类 的类名,首字母小写!
<!--实体类起别名--> <typeAliases> <package name="com.xie.pojo"/> </typeAliases>
实体类少的情况下用第一种方式,如果实体类多用第二种
在扫描包的情况下,又不想用默认的类名,使用注解
一些基本的类型别名,默认就存在的别名
设置(Setting)
映射器(mappers)
MapperRegistry:注册绑定我们的Mapper文件;
<mappers> <mapper resource="com/xie/dao/UserMapper.xml"/> </mappers>
方式二:使用class文件绑定注册
注意:接口和他的Mapper配置文件必须同名!
接口和他的Mapper配置文件必须在同一个包下!
方式三:
使用扫描包注入绑定
<mappers> <package name="com.xie.dao"/> </mappers>
注意:接口和他的Mapper配置文件必须同名!
接口和他的Mapper配置文件必须在同一个包下!
其他配置
typeHandlers 类型处理器
objectFactory 对象工厂
plugins 插件
生命周期和作用域(Scope)
生命周期和作用域,错误的使用会出现并发问题。
SqlSessionFactoryBuilder 一旦创建了SqlSessionFactory就不需要了,可以放在局部变量
SqlSessionFactory 如同数据库连接池 ,一旦创建在该应用运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例,如果多次重建会浪费内存资源,最佳作用域是应用作用域。最简单的就是使用单例模式或者静态单例模式
SqlSession 相当于数据库连接上的一个请求,每个线程都要有一个自己的连接实例, 最后需要关闭,最佳作用域是方法作用域,用完之后需要关闭,否则资源被占用。
解决属性名和字段名不一致的问题
解决办法:
1.起别名
<select id="getUserById" parameterType="int" resultType="User"> select id,name,pwd as password from mybatis.user where id = #{id} </select>
2.resultMap 结果集映射
id name pwd id name password <!--结果集映射--> <resultMap id="UserMap" type="User"> <!--colum数据库中的字段 property实体类中的属性--> <result column="pwd" property="password"/> </resultMap> <select id="getUserById" parameterType="int" resultMap="UserMap"> select * from mybatis.user where id = #{id} </select>
ResultMap对于简单的语句不需要配置显示映射结果,复杂的语句只需要描述他们的映射关系
日志
日志工厂
如果一个数据库操作,出现了异常,我们需要排错,日志就是最好的助手
日志工厂来实现:
SLF4J
LOG4J 掌握
LOG4J2
JDK_LOGGING
COMMONS_LOGGING
STDOUT_LOGGING 掌握
在Mybatis中具体使用哪一个日志实现,在设置中设定!
STDOUT_LOGGING标准日志输出
在核心配置文件中配置
</properties> <!--日志设置--> <settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings> <!--实体类起别名--> <typeAliases> <!-- <typeAlias type="com.xie.pojo.User" alias="User"/>--> <package name="com.xie.pojo"/> </typeAliases>
是Apache的开源项目,使用Log4j 可以控制日志信息输送的目的地是控制台,文件,GUI组件
可以控制每一条日志输出格式
通过定义每一条日志信息的级别,能够更加细致的控制日志的生成过程
通过配置文件来灵活的进行配置,而不需要修改应用的代码
1.先导包
2.配置日志文件
3.配置log4j为日志的实现
<settings> <!-- <setting name="logImpl" value="STDOUT_LOGGING"/>--> <setting name="logImpl" value="LOG4J"/> </settings> <!--实体类起别名--> <typeAliases>
分页查询
select * from user limit startIndex,pageSize; SELECT * FROM `user` LIMIT 3; 从0到3,不含3
@Test public void getUserByLimit(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); Map<String, Integer> map= new HashMap<String,Integer>(); map.put("startIndex",0); map.put("pageSize",3); List<User> userByLimit = mapper.getUserByLimit(map); for (User user : userByLimit) { System.out.println(user); } //关闭资源 sqlSession.close(); }
RowBounds类分页:面向对象
分页插件
pageHelper
注解开发
面向接口编程:
根因:解耦,可拓展,高复用,分层开发,共同标准,规范性更好
简单语句可以使用,但是复杂的要用xml映射更好
核心使用反射机制,获得类里面的方法,及相应的结果返回类型,注解的sql;本质反射,底层动态代理。
Mybatis的工作流程
Lombok
@Getter and @Setter@FieldNameConstants@ToString@EqualsAndHashCode@AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog@Data@Builder@SuperBuilder@Singular@Delegate@Value@Accessors@Wither@With@SneakyThrows@val@varexperimental @var@UtilityClass
@Data :无参构造,get,set,toString,hashcode,equals
多对一处理
多个学生,对应一个老师。对于学生而言,多个学生关联一个老师。【多对一】
对于老师而言,集合,一个老师,有很多学生【一对多】
sql:
按照嵌套查询处理
<mapper namespace="com.xie.dao.StudentMapper"> <!-- 1.查询所有的学生 2.根据查询出来学生的tid 寻找对应的老师 --> <select id="getStudent" resultMap="StudentTeacher"> select * from student </select> <resultMap id="StudentTeacher" type="student"> <result property="id" column="id"/> <result property="name" column="name"/> <!--复杂的属性 需要单独处理 如果是对象 association 集合 collection--> <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/> </resultMap> <select id="getTeacher" resultType="Teacher"> select * from teacher where id=#{tid} </select> </mapper>
按照结果嵌套处理
<mapper namespace="com.xie.dao.StudentMapper"> <!-- 按照结果嵌套查询--> <select id="getStudent2" resultMap="StudentTeacher2"> select s.id sid, s.name sname, t.name tname from student s,teacher t where s.tid = t.id </select> <resultMap id="StudentTeacher2" type="Student"> <result property="id" column="sid"/> <result property="name" column="sname"/> <association property="teacher" javaType="Teacher"> <result property="name" column="tname"/> </association> </resultMap>
Mysql多对一查询
1.子查询 2.联表查询
一对多处理
一个老师拥有多个学生
按照结果嵌套处理
按照查询嵌套处理
Data public class Teacher { private int id; private String name; //一个老师拥有多个学生 private List<Student> student; } @Data public class Student { private int id; private String name; //学生需要关联一个老师 private int tid; }
<select id="getTeacher" resultMap="TeacherStudent"> select s.id sid,s.name sname,t.name tname,t.id tid from student s,teacher t where s.tid=t.id and t.id=#{tid} </select> <resultMap id="TeacherStudent" type="teacher"> <result property="id" column="tid"/> <result property="name" column="tname"/> <!--collection javaType是指定属性的类型 集合中的泛型信息,我们使用ofType--> <collection property="student" ofType="Student"> <result property="id" column="sid"/> <result property="name" column="sname"/> <result property="tid" column="tid"/> </collection> </resultMap> <!-- ================子查询======================================--> <select id="getTeacher2" resultMap="TeacherStudent2"> select * from teacher where id = #{tid} </select> <resultMap id="TeacherStudent2" type="Teacher"> <collection property="student" column="id" javaType="ArrayList" ofType="Student" select="getStudentByTeacherID"/> </resultMap> <select id="getStudentByTeacherID" resultType="student"> select * from student where tid=#{tid} </select>
小结
1.关联 association 多对一
2.集合 collection 一对多 一个对象里面包含有多个其他对象
3.javaType 用来指定实体类中属性的类型
ofType 用来指定映射到List或者集合中的pojo类型,泛型中的约束类型
注意点:保证SQL的可读性 尽量保证通俗易懂
注意一对多 和多对一种,属性名和字段问题
如果问题不好排查,可以使用日志 建议使用Log4j
尽量避免慢SQL
动态SQL
根据不同的条件生成不同的SQL语句
if choose(when,otherwise) trim(weher,set) foreach
1、搭建环境
CREATE TABLE `blog`( `id` VARCHAR(50) NOT NULL COMMENT '博客id' , `title` VARCHAR(100) NOT NULL COMMENT '博客标题', `author` VARCHAR(30) NOT NULL COMMENT '博客作者', `create_time` DATETIME NOT NULL COMMENT '创建时间', `views` INT(30) NOT NULL COMMENT '浏览量' )ENGINE =INNODB DEFAULT CHARSET=utf8
@Data public class Blog { private String id; private String title; private String author; //跟数据库里的字段名不一致 可在xml核心配置开启驼峰命名转换 private Date createTime; private int views; }
<!--开启驼峰命名转换--> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>
public void addBlog(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); Blog blog=new Blog(); blog.setId(IDUtils.getUUID()); blog.setTitle("Mybatis动态SQL"); blog.setCreateTime(new Date()); blog.setAuthor("ERIC"); blog.setViews(666); mapper.addBlog(blog); blog.setId(IDUtils.getUUID()); blog.setTitle("java666"); mapper.addBlog(blog); blog.setId(IDUtils.getUUID()); blog.setTitle("Spring666"); mapper.addBlog(blog); blog.setId(IDUtils.getUUID()); blog.setTitle("微服务666"); mapper.addBlog(blog); sqlSession.close(); }
<select id="queryBlogIf" parameterType="map" resultType="Blog"> select * from blog where 1=1 <if test="title !=null"> and title=#{title} </if> <if test="author !=null"> and author=#{author} </if> </select>
select * from blog <where> <if test="title !=null"> and title=#{title} </if> <if test="author !=null"> and author=#{author} </if> </where> </select>
只要满足第一个,就结束了,只能满足其中一个条件。
<select id="queryBlogChoose" parameterType="map" resultType="Blog"> select * from blog <where> <choose> <when test="title !=null"> title=#{title} </when> <when test="author !=null"> and author=#{author} </when> <otherwise> and views =#{views} </otherwise> </choose> </where> </select>
<update id="updateBlog" parameterType="map"> update blog <set> <if test="titel !=null"> title=#{title}, </if> <if test="author !=null"> author=#{author} </if> </set> where id =#{id} </update>
trim 定制元素的一些功能
所谓的动态SQL,本质还是SQL语句,只是我们可以在SQL层面去执行一个逻辑代码
foreach
有的时候,我们可能会将一些功能的部分抽取出来,方便复用
1.使用sql标签抽取公共
2.在需要使用的地方使用include标签引用
<!--抽取出来的公共sql--> <sql id="if-title-author"> <if test="title !=null"> title=#{title} </if> <if test="author !=null"> and author =#{author} </if> </sql> <select id="queryBlogIf" parameterType="map" resultType="Blog"> select * from blog <where> <include refid="if-title-author"></include> </where> </select>
注意事项:
最好基于单表来定义SQL片段
复用的sql不要存在where标签
<!--select * from blog where 1=1 and (id=1 or id=2 or id=3) 我们现在传递一个万能的map,map中可以存在一个集合 --> <select id="queryBlogForeach" parameterType="map" resultType="blog"> select * from blog <where> <foreach collection="ids" item="id" open="and (" close=")" separator="or"> id=#{id} </foreach> </where> </select>
动态sql就是在拼接sql语句,我们只要保证sql的正确性,按照sql的格式,去排列组合就可以了
建议:现在mysql中写出完整的sql再对应的去修改成为我们的动态sql实现通用即可!
缓存
Cache : 存在内存中的临时数据
将用户经常查询的数据存放在缓存中,用户去查询数据就不用从磁盘上查询 ,经常查询且不经常改变的数据,可以使用缓存