Mybatis学习总结
Mybatis介绍
mybatis是一个优秀的基于java的持久层框架,它内部封装了jdbc,使开发者只需要关注sql语句身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。 mybatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。 采用ORM思想解决了实体和数据库映射的问题,对jdbc进行了封装,屏蔽了jdbc api底层访问细节,使我们不用与jdbc api打交道,就可以完成对数据库的持久化操作。
它是一个半自动化框架。
JDBC实现
public static void main(String[] args)
{
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//通过驱动管理类获取数据库链接
connection = DriverManager .getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8","root", "root");
//定义sql语句 ?表示占位符
String sql = "select * from user where username = ?";
//获取预处理
statement preparedStatement = connection.prepareStatement(sql);
//设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值 preparedStatement.setString(1, "王五");
//向数据库发出sql执行查询,查询出结果集
resultSet = preparedStatement.executeQuery();
//遍历查询结果集
while(resultSet.next())
{
System.out.println(resultSet.getString("id")+"
"+resultSet.getString("username"));
}
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
//释放资源
if(resultSet!=null)
{
try
{
resultSet.close();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
if(preparedStatement!=null)
{
try
{
preparedStatement.close();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
if(connection!=null)
{
try {
connection.close();
}
catch (SQLException e)
{ // TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//上边使用jdbc的原始方法(未经封装)实现了查询数据库表记录的操作。
jdbc存在问题:
1、数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
2、Sql语句在代码中硬编码,造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
3、使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。
4、对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便。
两者比较:
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定义输出结果的类型。
搭建Mybatis开发环境
配置文件模式
- 1.创建maven工程,并在pom.xml中导入Mybatis和其他相关包的依赖(mysql ,junit等)
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
</dependencies>
2.编写相关实体类(对应着数据库)User,和持久层接口 IUserDao(mybatis不用写实现哦,将这个部分放在了注解或者配置文件中了)。
3.编写持久层接口的映射文件IUserDao.xml。
要求:
创建位置:
必须和持久层接口在相同的包中。
名称:
必须以持久层接口名称命名文件名,扩展名是.xml
<!--约束 直接抄-->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.dao.IUserDao">
<!-- 配置查询所有操作 -->
<select id="findAll" resultType="com.itheima.domain.User"> select * from user </select>
</mapper>
4.编写SqlMapConfig.xml 配置
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 配置mybatis的环境 -->
<environments default="mysql">
<!-- 配置mysql的环境 -->
<environment id="mysql">
<!-- 配置事务的类型 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置连接数据库的信息:用的是数据源(连接池) -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ee50"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<!-- 告知mybatis映射配置的位置 每个.xml映射都要写!!!!!! -->
<mappers>
<mapper resource="com/itheima/dao/IUserDao.xml"/>
</mappers>
</configuration>
-properties(属性)
–property -settings(全局配置参数)
–setting -typeAliases(类型别名)
–typeAliase
–package
-typeHandlers(类型处理器)
-objectFactory(对象工厂)
-plugins(插件)
-environments(环境集合属性对象)
–environment(环境子属性对象)
—transactionManager(事务管理)
—dataSource(数据源)
-mappers(映射器)
–mapper
–package
解耦改进:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 配置properties classpath下定义properties文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/eesy
jdbc.username=root
jdbc.password=1234
-->
<properties resource="jdbcConfig.properties"></properties>
<!--使用typeAliases配置别名,它只能配置domain中类的别名 这样com.itheima.domain包下就不用写全限定类名了,默认为类本身名字 首字母大写或小写都可以 -->
<typeAliases>
<package name="com.itheima.domain"></package>
</typeAliases>
<!--配置环境-->
<environments default="mysql">
<!-- 配置mysql的环境-->
<environment id="mysql">
<!-- 配置事务 -->
<transactionManager type="JDBC"></transactionManager>
<!--配置连接池-->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</dataSource>
</environment>
</environments>
<!-- 配置映射文件的位置
注册指定包下的所有mapper接口 如:<package name="cn.itcast.mybatis.mapper"/> 注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。 -->
<mappers>
<package name="com.itheima.dao"></package>
</mappers>
</configuration>
5.使用:
//如果是增删改的话 还要提交事务 , 查询就不用
//1.读取配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建SqlSessionFactory的构建者对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//3.使用构建者创建工厂对象
SqlSessionFactory SqlSessionFactory factory = builder.build(in);
//4.使用SqlSessionFactory生产SqlSession对象
SqlSession session = factory.openSession();
//5.使用SqlSession创建dao接口的代理对象
IUserDao userDao = session.getMapper(IUserDao.class);
//6.使用代理对象执行查询所有方法 (或者定义通过增删改查方法,在sqlsession对象中提供selectList方法)
List<User> users = userDao.findAll(); for(User user : users) { System.out.println(user); }
//7.释放资源
session.close();
in.close();
注解模式
将以上做三点修改
1.把编写持久层接口的映射文件IUserDao.xml 删除。
2.在持久层接口的方法上方加入注解 @Select(“select * from user”) 代替xml文件作用
3.在SqlMapConfig.xml中把
<mapper resource="com/itheima/dao/IUserDao.xml"/>
改为
<mapper class="com.itheima.dao.IUserDao"/>
配置文件模式与注解使用详解
例1:
<select id="findById" resultType="com.itheima.domain.User" parameterType="int"> select * from user where id = #{uid} </select>
select :查询语句
id :对应持久层接口方法名
resultType :对应持久层接口方法返回类型(在没经过SqlMapConfig配置时,用全限定类名)
parameterType:对应持久层接口方法参数类型(基本类型和String可直接写)(如果是类名也得:在没经过SqlMapConfig配置时,用全限定类名)
sql语句中使用#{}字符: 它代表占位符,相当于原来jdbc部分所学的?,都是用于执行语句时替换实际的数据。 具体的数据是由#{}里面的内容决定的。 #{}中内容的写法: 由于数据类型是基本类型,所以此处可以随意写。
例2:
<insert id="saveUser" parameterType="com.itheima.domain.User"> insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address}) </insert>
insert:插入语句
parameterType:参数为类,由于我们保存方法的参数是 一个User对象,此处#{}要写User对象中的属性名称。 原则上应该是#{User.username} 但上面指定了实体类名称所以只要属性名就行。
这个是增删改操作 别忘了提交事务。
例3:
<insert id="saveUser" parameterType="USER">
<!-- 配置保存时获取插入的id -->
<selectKey keyColumn="id" keyProperty="id" resultType="int">
select last_insert_id();
</selectKey>
insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})
</insert>
注解
@Insert("insert into user(username,sex,birthday,address)values(#{username},#{sex},#{birthday},#{address})")
@SelectKey(keyColumn="id",keyProperty="id",resultType=Integer.class,before = false, statement = { "select last_insert_id()" })
int saveUser(User user);
这个目前没怎么懂,获取插入数据的id值
例4:
模糊查询
法一 :#{username}, “%王%”,加百分号。(一般使用)
法二: ‘%${value}%’ “王”
区别:
#{}表示一个占位符号 通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换,#{}可以有效防止sql注入。 #{}可以接收简单类型值或pojo属性值。 如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称。
表
示
拼
接
s
q
l
串
通
过
{}表示拼接sql串 通过
表示拼接sql串通过{}可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换,
可
以
接
收
简
单
类
型
值
或
p
o
j
o
属
性
值
,
如
果
p
a
r
a
m
e
t
e
r
T
y
p
e
传
输
单
个
简
单
类
型
值
,
{}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,
可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,{}括号中只能是value。
例5:
输入参数是QueryVo类,选择条件是QueryVo类中的属性User的属性 要写user.username.liqi
<select id="findByVo" resultType="com.itheima.domain.User" parameterType="com.itheima.domain.QueryVo"> select * from user where username like #{user.username}; </select>
例6
输出结果的封装:
如果数据库的字段和类中的属性一样(不区分大小写),就会自动封装。
如果不一样:两种方法
1.使用别名查询 数据库的字段 as 类属性名称
<!-- 配置查询所有操作 -->
<select id="findAll" resultType="com.itheima.domain.User">
select id as userId,username as userName,birthday as userBirthday, sex as userSex,address as userAddress from user
</select>
2.resultMap
<resultMap type="com.itheima.domain.User" id="userMap">
<id column="id" property="userId"/>
<result column="username" property="userName"/>
<result column="sex" property="userSex"/>
<result column="address" property="userAddress"/>
<result column="birthday" property="userBirthday"/>
</resultMap>
id标签:用于指定主键字段
result标签:用于指定非主键字段
column属性:用于指定数据库列名
property属性:用于指定实体类属性名称
使用时 返回
resultType=“com.itheima.domain.User” 改为 resultMap=“userMap”
<select id="findAll" resultMap="userMap"> select * from user </select>
注解
@Select("select * from user")
@Results(
id="userMap", value= {
@Result(id=true,column="id",property="userId"),
@Result(column="username",property="userName"),
@Result(column="sex",property="userSex"),
@Result(column="address",property="userAddress"),
@Result(column="birthday",property="userBirthday")
})
List<User> findAll();
@Select("select * from user where id = #{uid} ")
@ResultMap("userMap")
User findById(Integer userId);
id 是否是主键字段
column 数据库的列名
property需要装配的属性名
one 需要使用的@One注解(@Result(one=@One)())) 代替association
使用格式: @Result(column=" “,property=”",one=@One(select=""))
many 需要使用的@Many注解(@Result(many=@many)())) 代替collection
使用格式: @Result(property="",column="",many=@Many(select=""))
例7
动态查询
test 和 sql语句#{username} 中的应该是属性名
sql其他的是数据库字段名
<!-- 抽取重复的语句代码片段 -->
<sql id="defaultSql">
select * from user
</sql>
需求:实现 select * from user where username like #{username} and and address like #{address}:
如果存在#{username} 或者 #{address}的话
<select id="findByUser" resultType="user" parameterType="user">
<include refid="defaultSql"></include>
<where>
<if test="username!=null and username != '' ">
and username like #{username}
</if>
<if test="address != null">
and address like #{address}
</if>
</where>
</select>
需求:传入多个id查询用户信息,用下边两个sql实现:
SELECT * FROM USERS WHERE username LIKE ‘%张%’ AND (id =10 OR id =89 OR id=16) SELECT * FROM USERS WHERE username LIKE ‘%张%’ AND id IN (10,89,16)
<select id="findInIds" resultType="user" parameterType="queryvo">
<!-- select * from user where id in (1,2,3,4,5); -->
<include refid="defaultSql"></include>
<where>
<if test="ids != null and ids.size() > 0">
<!--这个id 应该是字段名 -->
<foreach collection="ids" open="id in ( " close=")" item="uid" separator=",">
#{uid}
</foreach>
</if>
</where>
</select>
例8
association 和 collection
一个account类中 有一个类属性 user 将其封装成 resultMap (account 这边没写全限定类名,在sqlmapconfig 配置过了)
<resultMap type="account" id="accountMap">
<id column="aid" property="id"/>
<result column="uid" property="uid"/>
<result column="money" property="money"/>
<!-- 它是用于指定从表方的引用实体属性的 association 一对一-->
<association property="user" javaType="user">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="sex" property="sex"/>
<result column="birthday" property="birthday"/>
<result column="address" property="address"/>
</association>
</resultMap>
注解
@Results(
id="accountMap",
value= {
@Result(id=true,column="id",property="id"),
@Result(column="uid",property="uid"),
@Result(column="money",property="money"),
@Result(column="uid",
property="user",
one=@One(select="com.itheima.dao.IUserDao.findById", fetchType=FetchType.EAGER) )
})
fetchType=FetchType.EAGER 立即查找 把这个该成LAZY就是延迟,一般一对一是立即加载
即user 类中有类列表属性 List
<resultMap type="user" id="userMap">
<id column="id" property="id"></id>
<result column="username" property="username"/>
<result column="address" property="address"/>
<result column="sex" property="sex"/>
<result column="birthday" property="birthday"/>
<!-- collection是用于建立一对多中集合属性的对应关系 ofType用于指定集合元素的数据类型 --> <collection property="accounts" ofType="account">
<id column="aid" property="id"/>
<result column="uid" property="uid"/>
<result column="money" property="money"/>
</collection>
</resultMap>
注解
@Results(
id="userMap",
value= {
@Result(id=true,column="id",property="userId"),
@Result(column="username",property="userName"),
@Result(column="sex",property="userSex"),
@Result(column="address",property="userAddress"), @Result(column="birthday",property="userBirthday"),
@Result(column="id",property="accounts", many=@Many( select="com.itheima.dao.IAccountDao.findByUid", fetchType=FetchType.LAZY) )
})
一般多对多是LAZY 延迟加载
Mybatis事务提交方式
手动提交
session.commit();
自动提交
session = factory.openSession(true);
Mybatis 延迟加载策略
延迟加载: 就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载. 好处:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
坏处: 因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降
一对多,多对多 延时加载
一对一,多对一 立即加载
1.在SqlMapConfig中配置延迟加载
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
需求 :查询账户信息同时查询用户信息。
association(一般立即加载)
<mapper namespace="com.itheima.dao.IAccountDao">
<resultMap type="account" id="accountMap">
<id column="aid" property="id"/>
<result column="uid" property="uid"/>
<result column="money" property="money"/>
<!-- 它是用于指定从表方的引用实体属性的 select 指定内容查询用户的唯一标志 column 用户根据id查询时 所需要的参数的值
或者 select: 填写我们要调用的 select 映射的 id column : 填写我们要传递给 select 映射的参数 -->
<association property="user" javaType="user" select="com.itheima.dao.IUserDao.findById"
column="uid">
</association>
</resultMap>
<select id="findAll" resultMap="accountMap">
select * from account
</select>
</mapper>
<mapper namespace="com.itheima.dao.IUserDao">
<!-- 根据id查询 -->
<select id="findById" resultType="user" parameterType="int" >
select * from user where id = #{uid}
</select>
</mapper>
List<Account> accounts = accountDao.findAll(); //只会查询accounts 延迟查询
//加了下面几句才会用到了user 出现user 查询下面的
for(Account account :accounts)
{
account.getUser();
}
注解 :
@Results(
id="accountMap",
value= {
@Result(id=true,column="id",property="id"),
@Result(column="uid",property="uid"),
@Result(column="money",property="money"),
@Result(column="uid",
property="user",
one=@One(select="com.itheima.dao.IUserDao.findById", fetchType=FetchType.LAZY) )
})
collection (一般延迟)
<resultMap type="user" id="userMap">
<id column="id" property="id"></id>
<result column="username" property="username"/>
<result column="address" property="address"/>
<result column="sex" property="sex"/>
<result column="birthday" property="birthday"/>
<!-- collection是用于建立一对多中集合属性的对应关系 ofType用于指定集合元素的数据类型 select是用于指定查询账户的唯一标识(账户的dao全限定类名加上方法名称) column是用于指定使用哪个字段的值作为条件查询 -->
<collection property="accounts" ofType="account" select="com.itheima.dao.IAccountDao.findByUid" column="id">
</collection>
</resultMap>
<!-- 配置查询所有操作 -->
<select id="findAll" resultMap="userMap"> select * from user </select>
<select id="findByUid" resultType="account" parameterType="int">
select * from account where uid = #{uid}
</select>
注解
@Results(
id="userMap",
value= {
@Result(id=true,column="id",property="userId"),
@Result(column="username",property="userName"),
@Result(column="sex",property="userSex"),
@Result(column="address",property="userAddress"), @Result(column="birthday",property="userBirthday"),
@Result(column="id",property="accounts", many=@Many( select="com.itheima.dao.IAccountDao.findByUid", fetchType=FetchType.LAZY ) )
})
Mybatis缓存
一级缓存
一级缓存是SqlSession范围的缓存,当调用SqlSession的修改,添加,删除,commit(),close()等。方法时,就会清空一级缓存。
<select id="findById" resultType="UsEr" parameterType="int" useCache="true">
连续两次查询同一个东西 且不调用SqlSession的修改,添加,删除,commit(),close()等,只会提交一句sql语句。第二次直接
二级缓存
二级缓存是mapper映射级别的缓存,多个SqlSession去操作同一个Mapper映射的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
开启二级缓存(默认开启)
1.在SqlMapConfig中配置 (默认配置好的)
<settings>
<!-- 开启二级缓存的支持 -->
<setting name="cacheEnabled" value="true"/>
</settings>
2.配置Mapper映射
<cache>标签表示当前这个mapper映射将使用二级缓存,区分的标准就看mapper的namespace值。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.dao.IUserDao"> <!-- 开启二级缓存的支持 -->
<cache></cache>
</mapper>
<!-- 根据id查询 -->
<select id="findById" resultType="user" parameterType="int" useCache="true">
select * from user where id = #{uid}
</select>
将UserDao.xml映射文件中的标签中设置useCache=”true”代表当前这个statement要使用二级缓存,如果不使用二级缓存可以设置为false。
注意:针对每次查询都需要最新的数据sql,要设置成useCache=false,禁用二级缓存。
测试`
SqlSession sqlSession1 = factory.openSession();
IUserDao dao1 = sqlSession1.getMapper(IUserDao.class);
User user1 = dao1.findById(41);
System.out.println(user1);
sqlSession1.close();//一级缓存消失
SqlSession sqlSession2 = factory.openSession();
IUserDao dao2 = sqlSession2.getMapper(IUserDao.class);
User user2 = dao2.findById(41);
System.out.println(user2); sqlSession2.close();
System.out.println(user1 == user2);
注解
1.配置
<settings>
<!-- 开启二级缓存的支持 -->
<setting name="cacheEnabled" value="true"/>
</settings>
2 在持久层接口中使用注解配置二级缓存
@CacheNamespace(blocking=true)//mybatis基于注解方式实现配置二级缓存 public interface IUserDao {}