mybatis笔记

MyBatis
jdbc问题总结如下


1、 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
2、 Sql语句在代码中硬编码,造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
3、 使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。
4、 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便。


MyBatis配置
SqlMapConfig.xml(mapper.xml) --> SqlSessionFactory --> SqlSess --> Executor -->Mapped Statement -->数据库


sql参数映射(HashMap,String ing 基本数据类型,Pojo)--> Mapped Statement --> Sql映射出结果(HashMap,String ing 基本数据类型,Pojo)


1.MyBatis 配置SqlMapConfig.xml,此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信息。
mapper.xml 文件即SQL映射文件,文件中配置了数据库的sql语句。此文件需要在SqlMapConfig.xml中加载
2.通过mybatis环境等配置信息构造SqlSessionFactory即会话工厂。
3.由会话工厂创建sqlSession即会话,操作数据库需要通过sqlsession进行
4.MyBatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器,一个是缓存执行器。
5.Mapped Statement也是mybatis一个底层封装对象,他包含了mybatis配置信息及sql映射信息等。
mapper.xml文件中一个sql对应一个Mapped Statement对象,sql的id即是Mapped Statement的id。
6.Mapped Statement对SQL执行输入参数进行定义,包括Hashmap,基本类型,pojo,Executor通过Mapped Statement在
执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStateme设置参数。
7.Mapped Statement对sql执行输出结果进行定义,包括HashMap,、基本类型、pojo,Executor通过 Mapped Statement
在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。


mybatis默认使用log4j作为输出日志信息。


<mapper namespace="test"></mapper>
namespace :命名空间,用于隔离sql语句,后面会讲另一层非常重要的作用。


在SqlMapConfig.xml中添加
//根据id获取用户的信息
<select id="findUserByid" parameterType="int" resultType="po.User">
select * from User where id = #{id}
</select>
parameterType:定义输入到SQL中的映射类型
resultType:定义结果映射类型


MyBatis 框架需要加载映射文件,将User.xml 添加在SqlMapConfig.xml
<mappers>
<mapper resource=""sqlmap/User.xml"/>
</mappers>


测试程序
public void MyBatis_first{
//会话工厂
private SqlSessionFactory sqlSessionFactory;


@Before//所有的测试方法之前都先执行这个方法 前置增强
public void createSqlSessionFactory() throws IOException {
//配置文件
String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);


//使用 sqlSessionFactoryBuilder 从xml配文件中创建 SqlSessionFactory
sqlSessionFactory = new sqlSessionFactoryBuilder().build(inputStream);
}


//根据id查询用户信息
@Test
public void testFindUserById(){
//数据库会话实例
SqlSession sqlSession = null;
try{
//创建数据库会话实例sqlSession
sqlSession = sqlSessionFactory.openSession();
//查询单个记录,根据用户id查询用户信息
User user = sqlSession.selectOne("test.findUserByid",1);
//输出用户信息
System.out.println(user);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}


}
}
}


selectOne 只能查询一条
selectList可以查询一条或多条记录。


mysql自增主键返回
通过修改SQL映射文件,可以将mysql自增主键返回
<insert id="insertUser" parameterType="mybatis.po.User">
//selectKey将主键返回,需要再返回
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
select LAST_INSERT_ID()
</selectKey>
insert into user(username,birthday,sex,address)
   values(#{username},#{birthday},#{sex},#{address});
</insert>
添加selectKey实现将主键返回
keyProperty:返回的主键存储在pojo中的那个属性
order:selectKey的执行顺序,是相对与insert语句来说,由于mysql的自增原理执行完insert语句之后才将主键生成,
所以这里selectKey的执行顺序为after
resultType:返回的主键是什么类型


Oracle使用序列生成主键
首先自定义一个序列且用于生成主键,selectKey使用如下:
<insert  id="insertUser" parameterType="cn.itcast.mybatis.po.User">
<selectKey resultType="java.lang.Integer" order="BEFORE" 
keyProperty="id">
SELECT 自定义序列.NEXTVAL FROM DUAL
</selectKey>
insert into user(id,username,birthday,sex,address) 
values(#{id},#{username},#{birthday},#{sex},#{address})
</insert>
注意这里使用的order是“BEFORE”


<!-- 删除用户 -->
<delete id="deleteUserById" parameterType="int">
delete from user where id=#{id}
</delete>



修改 映射文件
<!-- 更新用户 -->
<update id="updateUser" parameterType="cn.itcast.mybatis.po.User">
update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address}
where id=#{id}
</update>


// 更新用户信息
@Test
public void testUpdate() {
// 数据库会话实例
SqlSession sqlSession = null;
try {
// 创建数据库会话实例sqlSession
sqlSession = sqlSessionFactory.openSession();
// 添加用户信息
User user = new User();
user.setId(16);
user.setUsername("张小明");
user.setAddress("河南郑州");
user.setSex("1");
user.setPrice(1999.9f);
sqlSession.update("test.updateUser", user);
// 提交事务
sqlSession.commit();




Mybatis解决jdbc编程的问题
1、 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
解决:在SqlMapConfig.xml中配置数据链接池,使用连接池管理数据库链接。
2、 Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。
3、 向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。
解决:Mybatis自动将java对象映射至sql语句,通过statement中的parameterType定义输入参数的类型。
4、 对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。
解决:Mybatis自动将sql执行结果映射至java对象,通过statement中的resultType定义输出结果的类型。


SqlSession的使用范围
SqlSession中封装了对数据库的操作,如:查询、插入、更新、删除等。
通过SqlSessionFactory创建SqlSession,而SqlSessionFactory是通过SqlSessionFactoryBuilder进行创建。


SqlSessionFactoryBuilder
SqlSessionFactoryBuilder用于创建SqlSessionFacoty,SqlSessionFacoty一旦创建完成就不需要SqlSessionFactoryBuilder了,
因为SqlSession是通过SqlSessionFactory生产,所以可以将SqlSessionFactoryBuilder当成一个工具类使用,
最佳使用范围是方法范围即方法体内局部变量。


  SqlSessionFactory
SqlSessionFactory是一个接口,接口中定义了openSession的不同重载方法,SqlSessionFactory的最佳使用范围是整个应用运行期间,一旦创建后可以重复使用,通常以单例模式管理SqlSessionFactory。


SqlSession
SqlSession是一个面向用户的接口, sqlSession中定义了数据库操作,默认使用DefaultSqlSession实现类。


执行过程如下:
1、 加载数据源等配置信息
Environment environment = configuration.getEnvironment();
2、 创建数据库链接
3、 创建事务对象
4、 创建Executor,SqlSession所有操作都是通过Executor完成,mybatis源码如下:


if (ExecutorType.BATCH == executorType) {
      executor = newBatchExecutor(this, transaction);
    } elseif (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
if (cacheEnabled) {
      executor = new CachingExecutor(executor, autoCommit);
    }


5、 SqlSession的实现类即DefaultSqlSession,此对象中对操作数据库实质上用的是Executor
结论:
每个线程都应该有它自己的SqlSession实例。SqlSession的实例不能共享使用,它也是线程不安全的。因此最佳的范围是请求或方法范围。绝对不能将SqlSession实例的引用放在一个类的静态字段或实例字段中。
打开一个 SqlSession;使用完毕就要关闭它。通常把这个关闭操作放到 finally 块中以确保每次都能执行关闭。如下:
SqlSession session = sqlSessionFactory.openSession();
try {
  // do work
} finally {
  session.close();
}


原始Dao开发中存在以下问题:
Dao方法体存在重复代码:通过SqlSessionFactory创建SqlSession,调用SqlSession的数据库操作方法
调用sqlSession的数据库操作方法需要指定statement的id,这里存在硬编码,不得于开发维护。


Mapper动态代理方式
实现原理
Mapper接口开发方法只需要程序员编写Mapper接口(相当于Dao接口),由Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。
Mapper接口开发需要遵循以下规范:
1、 Mapper.xml文件中的namespace与mapper接口的类路径相同。
2、 Mapper接口方法名和Mapper.xml中定义的每个statement的id相同 
3、 Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同
4、 Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同


  Mapper.xml(映射文件)
定义mapper映射文件UserMapper.xml(内容同Users.xml),需要修改namespace的值为 UserMapper接口路径。将UserMapper.xml放在classpath 下mapper目录 下。
<mapper namespace="cn.itcast.mybatis.mapper.UserMapper">
<!-- 根据id获取用户信息 -->
。。。。。
</mapper>


Mapper.java(接口文件)
/**
 * 用户管理mapper
 */
Public interface UserMapper {
//根据用户id查询用户信息
public User findUserById(int id) throws Exception;
//添加用户信息
public void insertUser(User user)throws Exception; 
}
接口定义有如下特点:
1、 Mapper接口方法名和Mapper.xml中定义的statement的id相同
2、 Mapper接口方法的输入参数类型和mapper.xml中定义的statement的parameterType的类型相同
3、 Mapper接口方法的输出参数类型和mapper.xml中定义的statement的resultType的类型相同


加载UserMapper.xml文件
修改SqlMapConfig.xml文件:


  <!-- 加载映射文件 -->
  <mappers>
    <mapper resource="mapper/UserMapper.xml"/>
  </mappers>


2.4.5 测试


Public class UserMapperTest extends TestCase {


private SqlSessionFactory sqlSessionFactory;

protected void setUp() throws Exception {
//mybatis配置文件
String resource = "sqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//使用SqlSessionFactoryBuilder创建sessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}



Public void testFindUserById() throws Exception {
//获取session
SqlSession session = sqlSessionFactory.openSession();
//获取mapper接口的代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
//调用代理对象方法
User user = userMapper.findUserById(1);
System.out.println(user);
//关闭session
session.close();

}
Public void testInsertUser() throws Exception {
//获取session
SqlSession session = sqlSessionFactory.openSession();
//获取mapper接口的代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
//要添加的数据
User user = new User();
user.setUsername("张三");
user.setBirthday(new Date());
user.setSex("1");
user.setAddress("北京市");
//通过mapper接口添加用户
userMapper.insertUser(user);
//提交
session.commit();
//关闭session
session.close();
}


namespace
mybatis官方推荐使用mapper代理方法开发mapper接口,程序员不用编写mapper接口实现类,使用mapper代理方法时,输入参数可以使用pojo包装对象或map对象,保证dao的通用性。


SqlMapConfig.xml中配置的内容和顺序如下:


properties(属性)
settings(全局配置参数)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境集合属性对象)
environment(环境子属性对象)
transactionManager(事务管理)
dataSource(数据源)
mappers(映射器)




在classpath下定义db.properties文件,
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=mysql




SqlMapConfig.xml引用如下:


<properties resource="db.properties"/>
<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>


注意: MyBatis 将按照下面的顺序来加载属性:
在 properties 元素体内定义的属性首先被读取。 
然后会读取properties 元素中resource或 url 加载的属性,它会覆盖已读取的同名属性。 
最后读取parameterType传递的属性,它会覆盖已读取的同名属性。
因此,通过parameterType传递的属性具有最高优先级,resource或 url 加载的属性次之,最低优先级的是 properties 元素体内定义的属性。


settings(配置)
mybatis全局配置参数,全局参数将会影响mybatis的运行行为。


typeAliases(类型别名)


自定义别名:
在SqlMapConfig.xml中配置:
<typeAliases>
<!-- 单个别名定义 -->
<typeAlias alias="user" type="cn.itcast.mybatis.po.User"/>
<!-- 批量别名定义,扫描整个包下的类,别名为类名(首字母大写或小写都可以) -->
<package name="cn.itcast.mybatis.po"/>
<package name="其它包"/>
</typeAliases>


typeHandlers(类型处理器)
类型处理器用于java类型和jdbc类型映射,如下:


<select id="findUserById" parameterType="int" resultType="user">
select * from user where id = #{id}
</select>


mappers(映射器)
Mapper配置的几种方法:
<mapper resource=" " />
使用相对于类路径的资源
如:<mapper resource="sqlmap/User.xml" />


<mapper url=" " />
使用完全限定路径
如:<mapper url="file:///D:\workspace_spingmvc\mybatis_01\config\sqlmap\User.xml" />


<mapper class=" " />
使用mapper接口类路径
如:<mapper class="cn.itcast.mybatis.mapper.UserMapper"/>


注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。


<package name=""/>
注册指定包下的所有mapper接口
如:<package name="cn.itcast.mybatis.mapper"/>
注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。


Mapper.xml映射文件中定义了操作数据库的sql,每个sql是一个statement,映射文件是mybatis的核心。


#{}实现的是向prepareStatement中的预处理语句中设置参数值,sql语句中#{}表示一个占位符即?。
使用占位符#{}可以有效防止sql注入,在使用时不需要关心参数值的类型,mybatis会自动进行java类型和jdbc类型的转换。
#{}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称。




4.1.4 传递pojo包装对象


开发中通过pojo传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。


定义包装对象
定义包装对象将查询条件(pojo)以类组合的方式包装起来。
public class QueryVo {
private User user;
//自定义用户扩展类
private UserCustom userCustom;


mapper.xml映射文件
//查询用户列表 根据用户名称和用户性别查询用户列表
<select id="findUserList" parameterType="QueryVo" resultType="user">
select * from user where username = #{user.username} and sex={#user.sex}
</select>
 
说明:mybatis底层通过ognl从pojo中获取属性值:#{user.username},user即是传入的包装对象的属性。
queryVo是别名,即上边定义的包装对象类型。


resultType(输出类型)
resultType总结:
输出pojo对象和输出pojo列表在sql中定义的resultType是一样的。
返回单个pojo对象要保证sql查询出来的结果集为单条,内部使用session.selectOne方法调用,mapper接口使用pojo对象作为方法返回值。
返回pojo列表表示查询出来的结果集可能为多条,内部使用session.selectList方法,mapper接口使用List<pojo>对象作为方法返回值。


resultMap
resultType可以指定pojo将查询结果映射为pojo,但需要pojo的属性名和sql查询的列名一致方可映射成功。
如果sql查询字段名和pojo的属性名不一致,可以通过resultMap将字段名和属性名作一个对应关系 ,resultMap实质上还需要将查询结果映射到pojo对象中。
resultMap可以实现将查询结果映射为复杂类型的pojo,比如在查询结果映射对象中包括pojo和list实现一对一查询和一对多查询。

Mapper.xml定义
//查询用户列表 根据用户名和用户性别查询用户列表
<select id="findUserListResultMap" parameterType="queryVo" resultMap="userListResultMap">
select id id_,username username_,birthday birthday_ from user
</select>


定义resultMap
由于上边的mapper.xml 中 sql 查询和users.java 类属性不一致,需要定义resultMap:userListResultMap将sql查询列和Users.java类属性对应起来
type:最终映射的java对象
id:resultMap的唯一标识
<resultMap type="user" id="userListResultMap">
<id column="id_" property="id" />
<result column="username_" property="username"/>
<result column="birthday_" property="birthday"/>
</resultMap>
<id />:此属性表示查询结果集的唯一标识,非常重要。如果是多个字段为复合唯一约束则定义多个<id />。
Property:表示person类的属性。
Column:表示sql查询出来的字段名。
Column和property放在一块儿表示将sql查询出来的字段映射到指定的pojo类属性上。
<result />:普通结果,即pojo的属性




动态sql(重点)
通过mybatis提供的各种标签方法实现动态拼接sql。
<select id="findUserList" parameterType="user" resultType="user">
select * from user 
<where>
<if test="id!=null and id!=''">
and id=#{id}
</if>
<if test="username!=null and username!=''">
and username like '%${username}%'
</if>
</where>
</select>
<where />可以自动处理第一个and。


一对一查询 
定义专门的po类作为输出类型,其中定义了sql查询结果集所有的字段。此方法较为简单,企业中使用普遍。


一对多是多对多的特例,如下需求:
查询用户购买的商品信息,用户和商品的关系是多对多关系。
需求1:
查询字段:用户账号、用户名称、用户性别、商品名称、商品价格(最常见)
企业开发中常见明细列表,用户购买商品明细列表,
使用resultType将上边查询列映射到pojo输出。


需求2:
查询字段:用户账号、用户名称、购买商品数量、商品明细(鼠标移上显示明细)
使用resultMap将用户购买的商品明细列表映射到user对象中。


5.5 resultMap小结
resultType:
作用:
将查询结果按照sql列名pojo属性名一致性映射到pojo中。
场合:
常见一些明细记录的展示,比如用户购买商品明细,将关联查询信息全部展示在页面时,此时可直接使用resultType将每一条记录映射到pojo中,在前端页面遍历list(list中是pojo)即可。


resultMap:
使用association和collection完成一对一和一对多高级映射(对结果有特殊的映射要求)。


association:
作用:
将关联查询信息映射到一个pojo对象中。
场合:
为了方便查询关联信息可以使用association将关联订单信息映射为用户对象的pojo属性中,比如:查询订单及关联用户信息。
使用resultType无法将查询结果映射到pojo对象的pojo属性中,根据对结果集查询遍历的需要选择使用resultType还是resultMap。

collection:
作用:
将关联查询信息映射到一个list集合中。
场合:
为了方便查询遍历关联信息可以使用collection将关联信息映射到list集合中,比如:查询用户权限范围模块及模块下的菜单,可使用collection将模块映射到模块list中,将菜单列表映射到模块对象的菜单list属性中,这样的作的目的也是方便对查询结果集进行遍历查询。
如果使用resultType无法将查询结果映射到list集合中。






Mybatis一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,
第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,
从而提高查询效率。当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。Mybatis默认开启一级缓存。


Mybatis二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace,不同的sqlSession两次执行相同namespace下的sql语句且向sql中传递参数也相同即最终执行相同的sql语句,
第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。
Mybatis默认没有开启二级缓存需要在setting全局参数中配置开启二级缓存。




一级缓存区域是根据SqlSession为单位划分的。
每次查询会先从缓存区域找,如果找不到从数据库查询,查询到数据将数据写入缓存。
Mybatis内部存储缓存使用一个HashMap,key为hashCode+sqlId+Sql语句。value为从查询出来映射生成的java对象


二级缓存区域是根据mapper的namespace划分的,相同namespace的mapper查询数据放在同一个区域,如果使用mapper代理方法每个mapper的namespace都不同,此时可以理解为二级缓存区域是根据mapper划分。
每次查询会先从缓存区域找,如果找不到从数据库查询,查询到数据将数据写入缓存。
Mybatis内部存储缓存使用一个HashMap,key为hashCode+sqlId+Sql语句。value为从查询出来映射生成的java对象
sqlSession执行insert、update、delete等操作commit提交后会清空缓存区域


开启二级缓存:
在核心配置文件SqlMapConfig.xml中加入
<setting name="cacheEnabled" value="true"/>


6.3.4 测试


//获取session1
SqlSession session1 = sqlSessionFactory.openSession();
UserMapper userMapper = session1.getMapper(UserMapper.class);
//使用session1执行第一次查询
User user1 = userMapper.findUserById(1);
System.out.println(user1);
//关闭session1
session1.close();
//获取session2
SqlSession session2 = sqlSessionFactory.openSession();
UserMapper userMapper2 = session2.getMapper(UserMapper.class);
//使用session2执行第二次查询,由于开启了二级缓存这里从缓存中获取数据不再向数据库发出sql
User user2 = userMapper2.findUserById(1);
System.out.println(user2);
//关闭session2
session2.close();


刷新缓存
在mapper的同一个namespace中,如果有其它insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。


 设置statement配置中的flushCache="true" 属性,默认情况下为true即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。
如下:
<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User" flushCache="true">


mybatis逆向工程

自动生成简单SQL语句,和xml配置文件


猜你喜欢

转载自blog.csdn.net/weixin_39924752/article/details/80042954