java21-Mybatis笔记

314 阅读8分钟

1、简介

  • 持久化就是程序的数据在持久态和瞬时状态的转化的过程
  • 内存:断电即失
  • mybatis是持久层的框架,层是界限十分明显

2、 Mybatis的使用步骤

  • 首先需要连接数据库,导入Mybatis和连接数据库的依赖
  • 要进行操作数据库,需要获取到对应数据库的SQLSession,进行数据库的操作
  • 构建工具类,来获取SQLSession对象 SQLSessionFactoryBuilder->SQLSessionFactory->Sqlssssion
  • 问题:根据什么来创建指定数据库SQLSessionFactory
  • 需要加载关于Mybatis-config的配置文件,通过它来创建SQLSessionFactory
//对应的代码
String resource="mybatis-config.xml"  //配置的对应数据源的信息
InputStream resourceAsStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessonFactory = new SqlSeesionFactoryBuilder().build(resourceAsStream);

SqlSession sqlSession = sqlSessionFactory.openSession();

  • 接着在Dao层中编写一个类为UserDao,里面有关于pojo操作的方法,返回的是pojo的变量
  • 映射对应数据中的关系,类似于之前的实现类
  • 定义UserDaoImpl.xml(UserMapper) 通过nameSpace,进行和UserDao进行绑定
  • select标签进行绑定对应的方法和返回值 ** id=方法名** resultType=返回值的类型(返回值的类型也是对应的类的权限定名称,要不然我不晓得是谁)
  • 值得注意的一点是:对应的xml文件在maven工程中不能很好的识别,需要添加对应的依赖build,来识别xml文件的配置。还需要向Mybatis核心配置文件中进行注册,对应的标签为<mapper>,resource="com/kuang/dao/userMapper.xml"
  • main方法中,通过工具类MyBatisUtils进行获取SqlSession,进行执行数据库的操作
SqlSession sqlSession = MybatisUtils.getSqlsession();
******重要的一步,sqlSession加载的是接口的类
//如何执行对应的sql语句  通过反射这里进行获得,而反射只能反射对应的接口
UserDao userDao = sqlSession.getMapper(UserDao.class);
//获取到之后就能执行对应的方法,通过方法进行调用带mapper文件中的配置
userDao.getUserList();  //就可以映射到mapper文件去执行对应的方法
sqlsession.close();

总结:核心的三个类: SqlSessionFactoryBuilder、 SqlsessionFactory(默认是静态单例模式:只能被一次实例化)、 SqlSession

3、增删改查

回顾

  • namespace在mapper.xml文件中的,必须和接口的权限定名字相同
  • select id就是对应接口中的方法名 resultType是SQL语句的返回值,也是接口中方法的返回值 parameyerType对应的是方法中参数的类型
  • select * from User where id = #{id} 这个id代表着传过来的id
  • 接口中的方法 List<User> findUserById(int id);
添加一个user
<insert id="addUser" parameterType="user的全限定名称">
    //mybatis是数据库的名字,user是对应的表 (连上id才可以这样去写)  //后面的占位符对应的是user的属性,必须要11对应
    insert into mybatis.user(id,name,pwd) values (#{id},#{name},#{pwd});
</insert>

//main方法中添加User
UserDao userdao =  Sqlsession.getMapper(UserDao.calss);
userdao.addUser(new User(10,“xiaohong”,123456));  //添加的一个类的具体的实例,引用的传递,每次传的是一个新的地址
  • 增删改查之后必须要提交事务,通过sqlsession.commit();
  • 添加一个User时,编写Sql具有多个属性,需要将User类型转为Map类型,可以传递自定义的类型 int addUser(User user); int addUser(Map<String,Object>map); //定义为object就可以为任意类型了
  • 因为map普通的键可以实现一一映射,且查询的效率为O(1) ` 这样我们的id的名字就不需要和属性一一对应,且属性太多的时候,可以只插入其中的一部分 对应的insert为

insert into mybatis.user (id,name,pwd) values (#{UserId},#{UserName},#{Userpwd}) `

4、模糊查询

模糊查询最容易导致的问题-SQL注入
1、java代码执行的时候,传递通配符% %

//进行拼接字符串
List<User> userList = mapper.getUserLike("%曹%");

2、在SQL拼接中使用通配符

select * from mybatis.user where name like "%"#{value}"%"

5、配置解析

//配置多个环境
<configuration>
    <environments default="development">  //默认的环境
        <environment id="development">
            <transactionManager type="JDBC"/>  //事务的管理器
            <!--数据源-->  //加个池子,让并发web应用更加的流畅
            <dataSource type="POOLED">    
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
        
        <environment id="2">
            <transactionManager type="JDBC"/>  //事务的管理器
            <!--数据源-->  //加个池子,让并发web应用更加的流畅
            <dataSource type="POOLED">    
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
   //每一个mapper文件都需要在mybatis.xml核心配置文件中进行注册
<mappers>
    <mapper resource="com/ahu/dao/UserMapper.xml"></mapper>
</mappers>
</configuration>

问题1:如何传递数据库连接的信息,定义一个properties文件,通过标签进行读取

//注意
<properties resource="db.properties">  //注意文件名以properties结尾

//或者直接在proiperties中配置连接的信息
//注意这里的properties标签放在最上面
<properties>
    <property name="username" value="root"/>
    <property name="ped" value="123456"/>
</properties>

//如果在配置文件中配置,又在properties标签中配置,优先级是配置文件中的高

问题2、类型别名

给java类型设置一个短的名字,减少使用权限定名字的冗余 在配置文件中使用标签<typeAliases>进行配置,位置放在properties后面

//第一种
<typeAliases>  //类型别名
    <typeAlias type="com.ahu.pojo.User" alias="User"/>
</typeAliases>

//第二种
<typeAliases>  //类型别名
    <package name = "com.ahu.dao.User">
</typeAliases>
  • 第一种不太适合类比较多的情况
  • 第二种适合自定义实体类比较多的情况,默认为类的首字母小写
  • 也可以使用注解在每个实体类的开头@Aliase("自定义别名")

问题3、映射

//第一种:使用resource来进行映射
<mappers>   //这是路径名字
    <mapper resource="com/ahu/dao/UserMapper.xml"></mapper>
</mappers>

//第二种:使用calss进行映射
<mappers>
    <mapper calss="com.ahu.dao.UserDaoMapper.class"></mapper>
</mappers>
//第三种:使用package进行映射
<mappers>
    <mapper package="com.ahu.dao"></mapper>
</mappers>

注意:第一种不需要接口和.xml 文件文件名字相同,而剩下两种需要名字相同

6、结果集映射

<resultMap id="userMap" type="User">
//column指的是数据库中的字段,property实体类的属性  解决实体类的属性名字和数据库中的字段的映射
    <result column="id" property="id"/>
    <result column="name" property="name"/>
    <result column="pwd" property="password"/>
</resultMap>


//id 指的是接口中的方法名字
<select id="getUserId" resultMap="userMap">
    select * from mybatis.user where id = #{id};
</select>

问题: 一对多和多对一的关系

7、日志工厂

//默认日志的配置,注意这个setting放的位置  **stdout标准输出**
<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

//使用其他的日志类型,只需要进行改一下value配置  LOG4J,但不是默认的,需要导入配置文件

定义一个log4J.properties的文件

简单使用
//在一个类中进行获取信息
static Logger logger=Logger.getLogger(UserDaoTest.class);
public void testLog4j(){
    logger.info();
    logger.debug();
    logger.error();
}

8、分页

目的:减少数据的处理量

使用limit实现分页

//语法
select * from user limit startIndex,pagesize
select * from user limit 3;  #[0,n]

使用mybatis实现页面的分页,核心SQL

  • 接口
  • Mapper.xml
  • 测试

9、使用注解开发

面向接口的编程,省略编写XML文件

  • 1、注解在接口上实现
@Select("select * from user")
List<User> getUsers();
  • 2、需要在核心配置文件中绑定接口
<mappers>
    <mapper class="com.ahu.dao.UserMapper" />
</mappers>
  • 3、 底层实现 动态代理 + 反射
  • 4、使用注解进行增删改查 (使用注解指定的进行查询)
//方法中具有多个参数,在前面必须加上那个注解@param
@Select("select * from user where id = #{id}")
User getUserByID(@Param("id") int id)

@Insert(“insert into user(id,name,pwd) values (#{id},#{name},#{password})”)
int addUser(User user);

@Update("update user set name = #{name},pwd=#{password} where id=#{id}")
int updateUser(User user);

@Delete("delete from user where id = #{id}")
int deleteUser(int id);

//注意,参数前不加注解就这样的
@delete("delete from user where id = #{uid}")
int deleteUser(@Param("uid") int id);
关于@param的注解
  • 基本类型的参数或者String类型,需要加上
  • 引用类型不需要加
  • 如果只有一个基本类型的话,可以忽略,但是建议大家都加上
  • 我们在SQL文件中引用的就是我们这里的@Param("uid")中设定的属性名
补充#{}和${}的区别
  • #{}可以防止SQL的注入,一般只用#{}
  • 类似于prepareStatement和pareparement的区别

10、Mybatis详细的执行流程

  • 首先,创建MyBatis.xml文件,配置连接的信息 解析xml文件
  • 接着,需要连接数据库,采用的是SqlSession,则需要得到他
  • 通过SqlSeesionFactoryBuilder加载Mybatis核心配置文件,通过流的方式进行读取解析
  • 然后,得到SqlSessionFactory,通过它的方法openSession(),来得到Sqlsession
  • opensession()方法中设置为true的时候,则自动进行提交事务
  • main方法中,通过工具类获取到SqlSession,通过SqlSeesion的方法getMapper()方法,通过的反射的原理类似于加载接口,获取到接口,可以来执行接口中对应的方法
  • 增删改的时候更需要进行提交事务 sqlSession.commit();
  • 执行的时候有一个事务管理器,创建executor执行器,创建SqlSession,来实现CRUD,当执行失败的时候进行回滚

注意:新添加一个接口的方法,必须要到mabatis.xml配置中心进行注册

11、Lombok的使用


@Data  //自动配置get set方法  tostring等
@NoArgsConstructor  
@AllArgsConstructor  //有参数的构造方法会覆盖无参的构造方法

12、多对一(队形的映射)和一对多(集合的映射)的处理

问题:
1、查询所有学生的信息
2、根据学生的tid,寻找对应的老师 子查询

1、按照查询嵌套处理
<select id="getStudent" resultMap="Student">
    select * from student    //查询返回学生但是老师的那一列为null
</select>

//修改:必须关联teacher表   //resultMap对应的是结果映射
<select id="getStudent" resultMap="StudentTeacher">
        select * from student
</select>

//这里对于student来说,查出的老师属于多对一的关系,在这个多的定义里,所选择的是association
<resultMap id="StudentTeacher" type="Student">
    <result property = "id" column = "id"/>
    <result property = "name" column = "name"/>
    //复杂的属性,我们需要单独处理 对象:association 集合:collection   ?JPA
    <association property="teacher" column="tid" javatype="Teacher" select="getTeacher"/>
</resultMap>

<select id="getTeacher" resultType="Teacher">
    select * from teacher where id = #{id}
</select>

总的流程相当于:查询一个学生的信息,其中包含老师,但老师是一个对象,没有所包含的返回值,不能直
接返回一个Student类型,因此使用ResultMap进行结果集映射,对于多对一的关系,返回的是1.则使用的是
assoction,对应的属性值为teacher, 列为tid(是对应的student表中的teacher的列吗??? javaType与对
应的类进行绑定,select,对应的select的方法) 
2、按照结果嵌套处理 (这个更简单了点)
<select>
    select s.id sid,s.name sname, t.name tname
    from student s,teacher t
    where s.tid = t.id
</select>

<resultMap id="StudentTeacher" type="Student">
    <result property="id" column="sid"/>
    <result property="name" column="sname"/>
    <association property="teacher" javaType="teacher">
        <result property="name" column="tname">
    </assocition>
</resultMap>
3、按照结果嵌套处理(一对多的情况)
<select id="getTeacher" resultMap="TeacherStudent">
    select s.id sid,s.name sname, t.name tname, t.id tid
    form student s, teacher t
    where s.tid=t.id and t.id=#{tid}
</select>

<resultMap>
    <result property="id" column="tid">
    <result property="name" column="tname">
    //对应的集合
    //property对应的实体类的属性,使用ofType进行返回对应的类
    <collection property="students" ofType="Student">
        <result property="id" column="sid"/>
        <result property="name" column="sname">
        <result property="sid" column="tid">
    </collection>
</resultMap>

13、动态的SQL

根据不同的条件生成不同的SQL语句

1、if 标签

//场景使用:比如按照不同的条件进行筛选,则对应不同的if标签
对应的where的条件不同

select * from student
    if<test="age>10">
        and age=#{age}
    </if>
    if< test="name != null">
        and name=#{name}
    </if>
2、where标签
3、foreach标签
4、动态SQL代码片段

14、缓存

一级缓存

SQLsession缓存,与数据库同一次会话期间查询的数据会放在本地的缓存中
以后如果需要获取相同的数据,直接从缓存中去拿,没必要再去查询数据库

缓存失效的情况
  • 增删改操作,可能会改变原来的数据,所以必定会刷新缓存
  • 查询不同的东西
  • 查询不同的mapper 就是处理不同接口的方法
  • 手动清理缓存
二级缓存

操作:只需要在mapper文件中加入一个标签
开启:随要默认是开启的,可读性较低,在setting标签中,进行设置name=cacheEnabled和value=true

  • 全局缓存,基于nameSpace的缓存,即作用的是整个接口中的所有的方法,而不仅仅作用于sqlSeesion对应的只是接口中的一个方法
  • 工作机制:
  • 一个会话查询一条数据,这个数据就会放在当前会话的一级缓存中
  • 如果及绘画关闭了,一级缓存消失,将数据保存到耳机的缓存中
  • 新的会话查询的信息,就可以熊二级缓存获取信息
  • 不同的mapper查出的数据会放在自己对应的缓存中的
问题:
  • 我们需要将实体类进行序列化
  • 所有的数据都会先放在一级的缓存中,只有当绘画提交的时候才会将数据保存到二级的缓存
  • 当用户刚开始查缓存的时候先查的是二级缓存,看有没有
  • 也可以自定义缓存,使用Ehcache
  • Pagehelper是Mybatis的分页插件
  • 对应的Mybatis-plus也是插件,简化开发

Mybatis面试题

  • #{}和{}的使用? 答:**使用#有效可以防止SQL的注入**,类似于preparedStatement和statement. {}是Properties文件中的变量占位符,它可以用于标签属性值和sql内部,属于静态文本替换
  • Mybatis是如何进行分页的?分页插件的原理是什么? 答:Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页,可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
    使用limit实现分页
    select * from user limit startIndex,pagesize (开始索引和页面的大小)
  • Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理不? 答:Mybatis动态sql可以让我们在Xml映射文件内,以标签的形式编写动态sql,完成逻辑判断和动态拼接sql的功能,Mybatis提供了9种动态sql标签trim|where|set|foreach|if|choose|when|otherwise|bind。

其执行原理为,使用OGNL从sql参数对象中计算表达式的值,根据表达式的值动态拼接sql,以此来完成动态sql的功能

  • Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?

答:Mybatis仅支持association关联对象collection关联集合对象的延迟加载association指的就是一对一collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。

它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。

当然了,不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的。

延迟加载对应的是多个表的联合查询

  • Mybatis中如何执行批处理? 答:使用BatchExecutor完成批处理。