文章目录
案例源码和sql文件
链接:https://pan.baidu.com/s/1JMiiW0sOFfzUXuNBgR-b4Q
提取码:lpqb
复制这段内容后打开百度网盘手机App,操作更方便哦
mybatis-init文件中是MyBatis环境搭建和入门案例、CRUD操作章节的源码
mybatis-dynamicSql文件中是动态SQL章节的源码
mybatis-noe2many文件中是MyBatis中的多表查询一对一和一对多章节的源码
MyBatis环境搭建和入门案例
使用XML的方式
导入jar包坐标:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
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>
<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/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="13456"/>
</dataSource>
</environment>
</environments>
<!--指定映射配置文件的位置-->
<mappers>
<mapper resource="cn/example/dao/IUserDao.xml"></mapper>
</mappers>
</configuration>
User :
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
//getter和setter……
}
IUserDao:
public interface IUserDao {
List<User> findAll();
}
IUserDao.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="cn.example.dao.IUserDao">
<select id="findAll" resultType="cn.example.domain.User">
select * from user
</select>
</mapper>
测试:
public class MyBatisTest {
public static void main(String[] args) throws IOException {
//1. 读取配置文件
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
//2. 创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(inputStream);
//3. 使用工厂生产SqlSession对象
SqlSession sqlSession = factory.openSession();
//4. 使用SqlSession对象创建Dao接口的代理对象
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
//5. 通过代理对象执行方法
List<User> userList = userDao.findAll();
userList.stream().forEach(System.out::println);
//6. 释放资源
sqlSession.close();
inputStream.close();
}
}
使用注解的方式
使用注解的方式只需做如下两点修改
IUserDao :
public interface IUserDao {
@Select("select * from user")
List<User> findAll();
}
sqlMapConfig.xml配置文件:
只需将resource属性改为class属性指定dao接口所在包位置
<mappers>
<mapper class="cn.example.dao.IUserDao"></mapper>
</mappers>
CRUD操作
基本的CRUD操作
IUserDao 接口:
public interface IUserDao {
//查询所有用户
List<User> findAll();
//保存用户
void saveUser(User user);
//更新
void updateUser(User user);
//根据id删除
void deleteUser(Integer id);
//根据id查找
User findOneByid(Integer id);
//根据姓名模糊查询
List<User> findByName(String name);
//查询总记录数
Integer findTotal();
}
IUserDao.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="cn.example.dao.IUserDao">
<select id="findAll" resultType="cn.example.domain.User">
select * from user
</select>
<insert id="saveUser" parameterType="cn.example.domain.User">
<!-- 配置插入操作后,获取插入数据的自增id -->
<selectKey keyProperty="id" keyColumn="id" resultType="Integer" order="AFTER">
select last_insert_id();
</selectKey>
insert into user(username, birthday, sex, address) values(#{username}, #{birthday}, #{sex}, #{address})
</insert>
<update id="updateUser" parameterType="cn.example.domain.User">
update user set username=#{username}, birthday=#{birthday}, sex=#{sex}, address=#{address} where id = #{id}
</update>
<!--当只有一个基本类型的参数时,#{}中的值可以任意取名-->
<delete id="deleteUser" parameterType="java.lang.Integer">
delete from user where id = #{id}
</delete>
<select id="findOneByid" parameterType="java.lang.Integer" resultType="cn.example.domain.User">
select * from user where id = #{id}
</select>
<select id="findByName" parameterType="String" resultType="cn.example.domain.User">
<!-- select * from user where username like #{name}
推荐使用上面这种写法,因为${}采用的是字符串替换,#{}采用的是占位符?
-->
<!-- value是固定的,不能换成其他名称 -->
select * from user where username like '%${value}%'
</select>
<select id="findTotal" resultType="Integer">
select count(*) from user
</select>
</mapper>
传递pojo包装对象作为查询条件
MyBatis使用OGNL
表达式解析对象字段的值,#{}或者${}括号中的值为pojo属性名称
OGNL表达式全称Object Graphic Navigation Language,对象图导航语言,它是通过对象的取值方法来获取数据,在写法上省略了getXXX()。比如User中提供了getUsername()方法获取username属性值,使用OGNL表达式写法就是user.username获取属性值。
而在MyBatis中,直接写username,而不是user.username,因为在parameterType属性中已经提供了属性所属的类,所以此时不需要写对象名称,如<insert id="saveUser" parameterType="cn.example.domain.User"> insert into user(username, birthday, sex, address) values(#{username}, #{birthday}, #{sex}, #{address}) </insert>
IUserDao接口中增加方法:
//根据QueryVo查询用户
List<User> findUserByVo(QueryVo queryVo);
QueryVo:
public class QueryVo {
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
IUserDao.xml中增加mapper配置:
<select id="findUserByVo" parameterType="cn.example.domain.QueryVo" resultType="cn.example.domain.User">
select * from user where username like #{user.username}
</select>
结果类型的封装
OGNL表达式能够根据传递过来的User对象,将user.username替换到#{username}上的前提是User对象的username属性和#{username}中的名称相同,如果将User对象中的username属性改为name
,那么就需要在sql语句中写成#{name}
,但是对于返回值类型,因为数据库中的字段还是username
,这还能将name
值封装到User对象的username
属性中吗?答案是不行。
在Windows系统上,MySQL数据库不区分大小写,即username和userName是同一个字段,因此User对象中的属性是userName,数据库中的字段可以是username,当从数据库查询到结果的时候,username字段和userName属性能够匹配上从而封装到User对象中。
解决数据库字段和对于的实体属性名称不同的办法有两种:
- 写sql的时候给结果字段起别名,即
select user as username ...
执行效率高 - MyBatis提供的配置项
resultMap
开发效率高
使用resultMap
修改User类:
public class User implements Serializable {
private Integer userId;
private String userName;
private Date userBirthday;
private String userSex;
private String userAddress;
//getter和setter方法省略...
}
IUserDao.xml:
<!--配置结果集列名和实体类的属性对应关系-->
<resultMap id="userMap" type="cn.example.domain.User">
<!--主键字段的对应-->
<id property="userId" column="id"></id>
<!--非主键字段的对应-->
<result property="userName" column="username"></result>
<result property="userBirthday" column="birthday"></result>
<result property="userSex" column="sex"></result>
<result property="userAddress" column="address"></result>
</resultMap>
<select id="findAll" resultMap="userMap">
select * from user
</select>
<insert id="saveUser" parameterType="cn.example.domain.User">
<!-- 配置插入操作后,获取插入数据的自增id -->
<selectKey keyProperty="userId" keyColumn="id" resultType="Integer" order="AFTER">
select last_insert_id();
</selectKey>
insert into user(username, birthday, sex, address) values(#{userName}, #{userBirthday}, #{userSex}, #{userAddress})
</insert>
sqlMapConfig.xml相关配置
properties标签的resource、url属性用法
使用properties标签的resource属性和url属性可以指定数据库的配置文件
resources路径下 jdbcConfig.properties:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/eesy_mybatis
jdbc.username=root
jdbc.password=123456
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>
<!-- 配置properties
可以在标签内部配置连接数据库的信息。也可以通过属性引用外部配置文件信息
resource属性:
用于指定配置文件的位置,是按照类路径的写法来写,并且必须存在于类路径下。
url属性:
是要求按照Url的写法来写地址
-->
<!--<properties url="jdbcConfig.properties在磁盘上的路径"></properties>-->
<properties resource="jdbcConfig.properties"></properties>
<!--配置环境-->
<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>
<!--指定映射配置文件的位置-->
<mappers>
<mapper resource="cn/example/dao/IUserDao.xml"></mapper>
</mappers>
</configuration>
typeAliases标签
使用typeAliases可以给实体类起别名
<typeAliases>
<!--typeAlias用于配置别名。type属性指定的是实体类全限定类名。alias属性指定别名,当指定了别名就会区分大小写
<typeAlias type="cn.example.domain.User" alias="user"></typeAlias>-->
<!-- 用于指定要配置别名的包,当指定之后,该包下的实体类都会注册别名,并且类名就是别名,不再区分大小写-->
<package name="cn.example.domain"></package>
</typeAliases>
在IUserDao.xml中指定parameterType的时候使用user即可,无需指定全限定类名
<update id="updateUser" parameterType="user">
update user set username=#{username}, birthday=#{birthday}, sex=#{sex}, address=#{address} where id = #{id}
</update>
mappers标签
<!--指定映射配置文件IUserDao.xml的位置-->
<mappers>
<mapper resource="cn/example/dao/IUserDao.xml"></mapper>
</mappers>
<!--指定dao接口的位置-->
<mappers>
<mapper class="cn.example.dao.IUserDao"></mapper>
</mappers>
<!--无论是xml的配置还是注解的配置,都可以使用package标签-->
<mappers>
<package name="cn.example.dao"></package >
</mappers>
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>
<properties resource="jdbcConfig.properties"></properties>
<typeAliases>
<package name="cn.example.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>
<!--指定映射配置文件的位置-->
<mappers>
<package name="cn.example.dao"></package >
</mappers>
</configuration>
MyBatis中的连接池
<dataSource type="POOLED">
...
</dataSource>
dataSource的type属性指定了连接池类型,可以取值:
- POOLED
采用传统的javax.sql.DataSource
规范中的连接池,mybatis中针对规范的实现实现了一套自己的连接池技术 - UNPOOLED
采用传统的获取连接的方式,虽然也实现了javax.sql.DataSource
接口,但是并没有采用池的概念,即每次需要连接的时候建立连接,用完释放连接,而不是归还到池中 - JNDI
采用服务器提供的JNDI技术实现,来获取DataSource
对象,不同的服务器所能拿到的DataSource
不同,tomcat服务器采用的是dbcp连接池。如果不是web或者maven的war工程,不能使用JNDI
MyBatis中的事务控制
在openSession()
方法中传入参数true
,可以设置事务提交方式为手动提交,不传参数则为自动提交
//1. 读取配置文件
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
//2. 创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(inputStream);
//3. 使用工厂生产SqlSession对象
SqlSession sqlSession = factory.openSession(true);
//4. 使用SqlSession对象创建Dao接口的代理对象...
//5. 通过代理对象执行方法...
//6. 释放资源...
动态SQL
User:
public class User implements Serializable {
private Integer userId;
private String userName;
private Date userBirthday;
private String userSex;
private String userAddress;
//getter和setter、toString方法省略
}
QueryVo :
public class QueryVo {
private User user;
//getter和setter方法省略
}
IUserDao:
public interface IUserDao {
}
IUserDao.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="cn.example.dao.IUserDao">
<!--配置结果集列名和实体类的属性对应关系-->
<resultMap id="userMap" type="user">
<!--主键字段的对应-->
<id property="userId" column="id"></id>
<!--非主键字段的对应-->
<result property="userName" column="username"></result>
<result property="userBirthday" column="birthday"></result>
<result property="userSex" column="sex"></result>
<result property="userAddress" column="address"></result>
</resultMap>
</mapper>
if标签
IUserDao接口中增加方法:
//根据传入的条件查询
List<User> findUserByCondition(User user);
IUserDao.xml中增加配置:
<select id="findUserByCondition" parameterType="user" resultMap="userMap">
select * from user where 1=1
<if test="userName != null">
and username = #{userName}
</if>
<if test="userSex != null">
and usersex = #{userSex}
</if>
</select>
where标签
<select id="findUserByCondition" parameterType="user" resultMap="userMap">
select * from user
<where>
<if test="userName != null">
and username = #{userName}
</if>
<if test="userSex != null">
and usersex = #{userSex}
</if>
</where>
</select>
foreach和sql标签
select * from user where id in(1,2);
QueryVo中增加属性并生成getter和setter方法:
private List<Integer> ids;
IUserDao接口中增加方法:
//根据QueryVO中提供的ids集合查询用户
List<User> findUserInIds(QueryVo queryVo);
IUserDao.xml中增加配置:
<select id="findUserInIds" parameterType="queryVo" resultMap="userMap">
select * from user
<where>
<if test="ids != null and ids.size()>0">
<!-- #{uid}中的uid名称和item值一样 -->
<foreach collection="ids" open="and id in(" close=")" item="uid" separator=",">
#{uid}
</foreach>
</if>
</where>
</select>
抽取重复的sql语句
<select id="findAll" resultMap="userMap">
select * from user
</select>
可以改写为如下,以后用到select * from user
的地方都可以用<include refid="defaultUser"></include>
代替:
<sql id="defaultUser">
select * from user
</sql>
<select id="findAll" resultMap="userMap">
<include refid="defaultUser"></include>
</select>
MyBatis中的多表查询
User:
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
//getter和setter、toString方法省略
}
Account :
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
//getter和setter、toString方法省略
}
IUserDao :
public interface IUserDao {
//查询所有用户以及用户下所有的账户信息(一对多)
List<User> findAll();
}
IAccountDao:
public interface IAccountDao {
//查询所有账户以及账户对应的用户(一对一)
List<Account> findAll();
}
一对一
关键标签:
association
Account类中增加属性,并设置getter和setter方法:
private User user;
IAccountDao.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="cn.example.dao.IAccountDao">
<!-- 定义封装account和user的resultMap -->
<resultMap id="accountUserMap" type="account">
<id property="id" column="aid"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<!-- 一对一的关系映射:配置封装user的内容
property:属性名称
column:联合查询时,account表连接字段名称
javaType:属性的类型
-->
<association property="user" column="uid" javaType="user">
<id property="id" column="id"></id>
<result column="username" property="username"></result>
<result column="address" property="address"></result>
<result column="sex" property="sex"></result>
<result column="birthday" property="birthday"></result>
</association>
</resultMap>
<!-- 查询所有 -->
<select id="findAll" resultMap="accountUserMap">
select u.*,a.id as aid,a.uid,a.money from account a , user u where u.id = a.uid;
</select>
</mapper>
一对多
关键标签:
collection
User类添加属性:
private List<Account> accounts;
<?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="cn.example.dao.IUserDao">
<!--一对多映射:定义user的resultMap-->
<resultMap id="userAccountMap" type="user">
<id property="id" column="id"></id>
<result column="username" property="username"></result>
<result column="address" property="address"></result>
<result column="sex" property="sex"></result>
<result column="birthday" property="birthday"></result>
<!--配置user对象中accounts集合的映射
property:属性名称
ofType:集合中元素的类型
-->
<collection property="accounts" ofType="account">
<id property="id" column="aid"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
</collection>
</resultMap>
<select id="findAll" resultMap="userAccountMap">
select * from user u left join account a on u.id=a.uid
</select>
</mapper>
多对多
多对多可以看成是一对多,在表结构上是多对多的关系,比如用户和角色,一个用户可以有多个角色,一个角色可以有多个用户,但是在sql层面,它们就是一对多的关系,一个用户可以有多个角色。因此写法和一对多的类似,只是slq语句上有变化。
MyBatis立即加载和延迟加载
MyBatis缓存
MyBatis使用数据缓存减少和数据库的交互次数,从而提高程序执行效率
适用于缓存的情况:
- 经常查询并且不经常改变的数据
- 能容忍缓存和数据库数据的不一致
一级缓存
一级缓存指的是MyBatis中SqlSession对象的缓存,当我们执行查询之后,查询的结果会同时存入到SqlSession提供的一块区域中,该区域的结构是一个Map。当我们再次查询同样的数据时,MyBatis会先去SqlSession查询是否有该数据,如果有则直接拿来用。当SqlSession对象消失时,MyBatis的一级缓存消失。
public void testFindOneByid() {
User user1 = userDao.findOneByid(41);
User user2 = userDao.findOneByid(41);
System.out.println(user1 == user2); //true
}
执行了一次sql:
public void testFindOneByid() {
User user1 = userDao.findOneByid(41);
//关闭SqlSession,缓存清空
sqlSession.close();
sqlSession = factory.openSession();
userDao = sqlSession.getMapper(IUserDao.class);
User user2 = userDao.findOneByid(41);
System.out.println(user1 == user2); //false
}
执行了两次sql:
一级缓存清空时机:
一级缓存是SqlSession范围的缓存,当调用SqlSession的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。因此查询-修改-查询,第二次查询走的是查数据库而不是查缓存。
二级缓存
二级缓存指的是MyBatis中SqlSessionFactory对象的缓存,由同一个SqlSessionFactory对象创建的SqlSession共享同一个缓存。
使用二级缓存:
sqlMapConfig.xml中添加配置:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
IUserDao.xml中开启支持二级缓存:
<mapper namespace="cn.example.dao.IUserDao">
<!--开启支持二级缓存-->
<cache/>
<select id="findOneByid" parameterType="java.lang.Integer" resultType="user" useCache="true">
select * from user where id = #{id}
</select>
</mapper>
使用:
public class SecondLevelCacheTest {
private InputStream in;
private SqlSessionFactory factory;
@Before//用于在测试方法执行之前执行
public void init()throws Exception{
//1.读取配置文件,生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取SqlSessionFactory
factory = new SqlSessionFactoryBuilder().build(in);
}
@After//用于在测试方法执行之后执行
public void destroy()throws Exception{
in.close();
}
@Test
public void testFirstLevelCache(){
SqlSession sqlSession1 = factory.openSession();
IUserDao dao1 = sqlSession1.getMapper(IUserDao.class);
User user1 = dao1.findOneByid(41);
System.out.println(user1);
sqlSession1.close();
SqlSession sqlSession2 = factory.openSession();
IUserDao dao2 = sqlSession2.getMapper(IUserDao.class);
User user2 = dao2.findOneByid(41);
System.out.println(user2);
sqlSession2.close();
System.out.println(user1 == user2); //false
}
}
user1和user2值不相等,但是第二次查询的确查的是缓存,这是为什么?因为MyBatis中的二级缓存保存的是数据而不是地址,当程序缓存中有的内存时,将该内容复制一份给新的变量,因此user1和user2的地址值不同。