Mybatis简介 MyBatis 本是apache的一个开源项目iBatis ,2010年这个项目由apache software foundation迁移到了google code,并且改名为Mybatis。2013年11月迁移到Github。 iBatis一词源于”internet”和”abatis”的组合,是一个基于Java的持久层框架。iBatis提供的持久层框架包括SQL Maps和Data Access Objects(DAOS)。 mybatis 内部封装了 jdbc,使开发者只需要关注 sql 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。 mybatis 通过 xml 或注解的方式将要执行的各种 statement 配置起来,并通过 java 对象和 statement 中 sql 的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回。 采用 ORM 思想解决了实体和数据库映射的问题,对 jdbc 进行了封装,屏蔽了 jdbc api 底层访问细节,使我们不用与 jdbc api 打交道,就可以完成对数据库的持久化操作。
Mybatis快速入门 基于dao的代理实现(默认方式) 1.创建mavan工程
2.添加Mybatis坐标 在项目的pom.xml
文件中添加Mybatis3.4.5坐标:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <dependencies > <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 > <scope > runtime</scope > </dependency > <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <version > 4.10</version > <scope > test</scope > </dependency > <dependency > <groupId > log4j</groupId > <artifactId > log4j</artifactId > <version > 1.2.12</version > </dependency > </dependencies >
3.添加实体类User
在domain
包下创建实体类User
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class User implements Serializable { private Integer id; private String username; private Date birthday; private String sex; private String address; public Integer getId () { return id; } public void setId (Integer id) { this .id = id; } public String getUsername () { return username; } public void setUsername (String username) { this .username = username; } public Date getBirthday () { return birthday; } public void setBirthday (Date birthday) { this .birthday = birthday; } public String getSex () { return sex; } public void setSex (String sex) { this .sex = sex; } public String getAddress () { return address; } public void setAddress (String address) { this .address = address; } @Override public String toString () { return "User [id=" + id + ", username=" + username + ", birthday=" + birthday + ", sex=" + sex + ", address=" + address + "]" ; } }
4.编写持久层接口IUserDao
在dao
包下创建实体类IUserDao
:
1 2 3 4 5 6 7 8 public interface IUserDao { List<User> findAll () ; }
5.编写持久层接口的映射文件IUserDao.xml
1 2 3 4 5 6 7 8 9 10 <?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 >
注意:
创建位置 必须和持久层接口在相同的包中;
名称 必须以持久层接口名称命名文件名,扩展名是.xml。
6.编写SqlMapConfig.xml
配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <?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" > <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 > <mappers > <mapper resource ="com/itheima/dao/IUserDao.xml" /> </mappers > </configuration >
7.编写测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class MybatisTest { public static void main (String[] args) throws Exception { InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml" ); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(in); SqlSession session = factory.openSession(); IUserDao userDao = session.getMapper(IUserDao.class); List<User> users = userDao.findAll(); for (User user : users) { System.out.println(user); } session.close(); in.close(); } }
当满足了如下3点规则,就无须再编写dao的实现类,mybatis自动创建实现类:
mybatis映射配置文件(mapper.xml)位置必须和dao接口的包结构相同;
映射配置文件的mapper标签namespace属性的取值必须是dao接口的全限定类名;
映射配置文件的操作配置(select)id属性的取值必须是dao接口的方法名;
基于注解的mybatis使用 1.在持久层接口IUserDao
中添加注解
1 2 3 4 5 6 7 8 public interface IUserDao { @Select("select * from user") List<User> findAll () ; }
2.不再需要持久层接口的映射文件IUserDao.xml
,直接删除
3.修改SqlMapConfig.xml
,xml路径改为类名
1 2 3 4 <mappers > <mapper class ="com.itheima.dao.IUserDao" /> </mappers >
自己实现dao的实现类 1.创建持久层接口IUserDao
的实现类impl.UserDaoImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class UserDaoImpl implements IUserDao { private SqlSessionFactory factory; public UserDaoImpl (SqlSessionFactory factory) { this .factory = factory; } public List<User> findAll () { SqlSession session = factory.openSession(); List<User> users = session.selectList("com.itheima.dao.IUserDao.findAll" ); session.close(); return users; } }
2.修改测试类的调用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class MybatisTest { public static void main (String[] args) throws Exception { InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml" ); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(in); IUserDao userDao = new UserDaoImpl(factory); List<User> users = userDao.findAll(); for (User user : users) { System.out.println(user); } in.close(); } }
Mybatis基本操作 在上面的快速入门中介绍了最基本的查询操作,接下来介绍mybatis对数据库的增删查改操作
基于代理的增删查改CRUD 1.在持久层接口IUserDao
中添加接口方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 User findById (Integer userId) ;void saveUser (User user) ;void updateUser (User user) ;void deleteUser (Integer userId) ;List<User> findByName (String username) ;
2.在用户的映射配置文件SqlMapConfig.xml
中增加配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <select id ="findById" parameterType ="int" resultType ="com.itheima.domain.User" > select * from user where id = #{uid}</select > <insert id ="saveUser" parameterType ="com.itheima.domain.User" > <selectKey keyProperty ="id" keyColumn ="id" resultType ="int" order ="AFTER" > select last_insert_id(); </selectKey > insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday});</insert > <update id ="updateUser" parameterType ="com.itheima.domain.User" > update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id}</update > <delete id ="deleteUser" parameterType ="java.lang.Integer" > delete from user where id = #{uid}</delete > <select id ="findByName" parameterType ="string" resultType ="com.itheima.domain.User" > select * from user where username like #{name} </select >
resultType 属性: 用于指定结果集的类型。
parameterType 属性: 用于指定传入参数的类型。
sql语句中的#{}
: 表示占位符,相当于原来jdbc的?
,都是用于执行语句时替换实际的数据,具体的数据由#{}
里面的内容决定的,如果数据类型是基本类型,里面可以填写任意名字,比如findById
中的uid
可以对应接口方法中的实际参数名userId
。如果数据类型是复杂类型,语法格式就是使用 #{对象.对象}
的方式,比如#{user.username}
它会先去找 user 对象,然后在 user 对象中找到 username 属性,并调用getUsername()方法把值取出来,我们在 parameterType 属性上指定了实体类名称,所以可以省略user.
,而直接写 username。
#{}
和${}
的区别:
#{}
表示一个占位符号,通过#{}
可以实现 preparedStatement 向占位符中设置值,自动进行 java 类型和 jdbc 类型转换,#{}
可以有效防止 sql 注入。 #{}
可以接收简单类型值或 pojo 属性值。 如果 parameterType 传输单个简单类型值,#{}
括号中可以是 value 或其它任意名称。
${}
表示拼接 sql 串,通过${}
可以将 parameterType 传入的内容拼接在 sql 中且不进行 jdbc 类型转换, ${}
可以接收简单类型值或 pojo 属性值,如果 parameterType 传输单个简单类型值,${}
括号中只能是 value ,或者在接口方法中声明参数名:List<User> findByName(@Param(value="name") String username);
。
3.编写测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class MybatisTest { private InputStream in; private SqlSession sqlSession; private IUserDao userDao; @Before public void init () throws Exception { in = Resources.getResourceAsStream("SqlMapConfig.xml" ); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); sqlSession = factory.openSession(); userDao = sqlSession.getMapper(IUserDao.class); } @After public void destroy () throws Exception { sqlSession.commit(); sqlSession.close(); in.close(); } @Test public void testFindOne () { User user = userDao.findById(50 ); System.out.println(user); } @Test public void testFindByName () { List<User> users = userDao.findByName("%王%" ); for (User user : users){ System.out.println(user); } } }
4.参数中如果需要传递多个Pojo(Plain Ordinary Java Object)对象,可以用一个pojo类QueryVo
包含多个pojo
1 2 3 4 5 6 7 8 9 public class QueryVo { private User user; public User getUser () { return user; } public void setUser (User user) { this .user = user; } }
1 2 3 4 <select id ="findByVo" resultType ="com.itheima.domain.User" parameterType ="com.itheima.domain.QueryVo" > select * from user where username like #{user.username};</select >
配置文件的主要标签说明 SqlMapConfig.xml文件配置的内容和层级关系:
-properties(属性) —property -settings(全局配置参数) —setting-typeAliases(类型别名) —typeAliase —package -typeHandlers(类型处理器) -objectFactory(对象工厂) -plugins(插件) -environments(环境集合属性对象) —environment(环境子属性对象) —-transactionManager(事务管理) —-dataSource(数据源)-mappers(映射器) —mapper —package
返回值映射resultMap
sql语句中的查询返回列必须和resultType
类型中的属性名对应,否则会赋值为null
,如果仅大小写不同,仍可以赋值成功,因为mysql 在 windows 系统中不区分大小写! 如果要匹配,可以在sql语句中设置别名强制对应上,此方法要可能要修改大量的sql语句,工作量较大,可以考虑另一种方法,对返回值做映射: 1.在IUserDao.xml
中定义resultMap
1 2 3 4 5 6 7 8 9 10 <resultMap id ="userMap" type ="com.itheima.domain.User" > <id property ="userId" column ="id" > </id > <result property ="userName" column ="username" > </result > <result property ="userAddress" column ="address" > </result > <result property ="userSex" column ="sex" > </result > <result property ="userBirthday" column ="birthday" > </result > </resultMap >
2.在需要映射的地方将返回类型由resultType
改为resultMap
1 2 3 4 <select id ="findById" parameterType ="int" resultMap ="userMap" > select * from user where id = #{uid}</select >
属性标签配置properties
可将一些配置信息抽离出来保存在properties
标签中,实现复用,修改SqlMapConfig.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <properties > <property name ="driver" value ="com.mysql.jdbc.Driver" > </property > <property name ="url" value ="jdbc:mysql://localhost:3306/eesy_mybatis" > </property > <property name ="username" value ="root" > </property > <property name ="password" value ="1234" > </property > </properties > ... <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 >
也可将properties抽离出来,放在单独的文件,在 classpath 下定义jdbcConfig.properties
文件
1 2 3 4 jdbc.driver =com.mysql.jdbc.Driver jdbc.url =jdbc:mysql://localhost:3306/eesy jdbc.username =root jdbc.password =1234
SqlMapConfig.xml
的properties
标签设置url
或者resources
属性,用于引用jdbcConfig.properties
文件,两者区别如下:
类名别名标签typeAliases
1 2 3 4 5 6 7 8 9 10 <typeAliases > <-- <typeAlias type ="com.itheima.domain.User" alias ="user" > </typeAlias > --> <package name ="com.itheima.domain" > </package > </typeAliases >
映射器标签mappers
1 2 3 4 5 6 7 8 9 10 11 <mappers > <package name ="com.itheima.dao" > </package > </mappers >
配置连接池 在 Mybatis 的SqlMapConfig.xml
配置文件中,通过<dataSource type="pooled">
来实现 Mybatis 中连接池的配置。 Mybatis 将它自己的数据源分为三类:
POOLED:采用传统的javax.sql.DataSource规范中的连接池,mybatis中有针对规范的实现
UNPOOLED:采用传统的获取连接的方式,虽然也实现Javax.sql.DataSource接口,但是并没有使用池的思想
JNDI:采用服务器提供的JNDI技术实现,来获取DataSource对象,不同的服务器所能拿到DataSource是不一样的
MyBatis内部定义了实现了java.sql.DataSource
接口的UnpooledDataSource
类和PooledDataSource
类分别表示UNPOOLED、POOLED类型的数据源。
Mysql的数据库事务 数据库事务(transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。
事务的四大特性(ACID)
原子性(Atomicity) :事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。
一致性(Consistency) :事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。
隔离性(Isolation) :同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。
持久性(Durability) :事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。
事务并发带来的三个问题
脏读 :事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据。
不可重复读 :事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。
幻读 :系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行(Repeatable read),解决幻读需要锁表。
MySQL定义的四种事务隔离级别
事务隔离级别
脏读
不可重复读
幻读
解释
读未提交(read-uncommitted)
是
是
是
可以读到未提交的事物
不可重复读(read-committed)
否
是
是
只能读提交的事物
可重复读(repeatable-read)
否
否
是
事务提交前后都能读【MySql默认】
串行化(serializable)
否
否
否
serializable时会锁表,是最安全的,也是日常开发基本不会用
Mysql事务设置命令
读未提交set session transaction isolation level read uncommitted;
不可重复读(读已提交的)set session transaction isolation level read committed;
可重复读set session transaction isolation level repeatable read;
串行化set session transaction isolation level serializable;
Mybatis中使用事务
Mybatis是基于jdbc的封装,在 JDBC 中可通过setAutoCommit()
将事务提交设置为自动或手动的方式。
setAutoCommit
官方定义:
将此连接的自动提交模式设置为给定状态。如果连接处于自动提交模式下,则它的所有SQL语句将被执行并作为单个事务提交。否则,它的SQL语句将聚集到事务中,直到调用commit
方法或 rollback
方法为止。默认情况下,新连接处于自动提交模式。
在mybatis的session = factory.openSession();
方法中,默认参数是false
,即手动提交事务的方式,在执行完sql语句后,需手动执行session.commit
或session.rollback
,这也是常用的方式,因为我们可以根据业务情况来决定是否进行提交。 如果设置`session = factory.openSession(true);
,则mybatis会设置jdbc为自动提交事务的方法,无须显示执行session.commit
。
Mybatis动态Sql语句 代码片段 可将重复的sql语句抽离出来,使用时用include引用即可,达到 sql 重用的目的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <sql id ="defaultSql" > select * from user</sql > <select id ="findAll" resultType ="user" > <include refid ="defaultSql" > </include > </select > <select id ="findById" resultType ="user" parameterType ="int" > <include refid ="defaultSql" > </include > where id = #{uid}</select >
动态语句<if>
可以根据条件来判断是否需要拼接sql语句,标签中的 test 属性中写的是对象的属性名,如果是包装类的对象要使用 OGNL 表达式的写法。 为保证拼接顺利,主语句上要加上where 1=1
。
1 2 3 4 5 6 7 8 9 <select id ="findByUser" resultType ="user" parameterType ="user" > select * from user where 1=1 <if test ="username!=null and username != '' " > and username like #{username} </if > <if test ="address != null" > and address like #{address} </if > </select >
动态语句<where>
使用<where>
标签可以简化where 1=1
的条件封装,同时可以自动处理条件掉表达式开头的and
或者or
字符,使生成正确的sql语句。
1 2 3 4 5 6 7 8 9 10 11 12 <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 >
动态语句<foreach>
在拼接sql语句时,有时需要传递一个集合,比如这样的sql语句select 字段 from user where id in (?)
,可以写这样的语句:
1 2 3 4 5 6 7 8 9 10 11 <select id ="findUserInIds" resultMap ="userMap" parameterType ="queryvo" > <include refid ="defaultUser" > </include > <where > <if test ="ids != null and ids.size()>0" > <foreach collection ="ids" open ="and id in (" close =")" item ="uid" separator ="," > #{uid} </foreach > </if > </where > </select >
<foreach>
标签用于遍历集合,它的属性含义为:
collection:代表要遍历的集合元素,注意编写时不要写#{}
open:代表语句的开始部分
close:代表结束部分
item:代表遍历集合的每个元素,生成的变量名
sperator:代表分隔符
测试方法,构造一个ids的集合:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Test public void testFindInIds () { QueryVo vo = new QueryVo(); List<Integer> list = new ArrayList<Integer>(); list.add(41 ); list.add(42 ); list.add(46 ); vo.setIds(list); List<User> users = userDao.findUserInIds(vo); for (User user : users){ System.out.println(user); } }
多表查询 一对一的association标签 如果2个表关联查询,比如Account表和User表,一个Account只能对应一个User,Account对User是1对1的关系,可以采用继承 的方式让Account继承User的全部字段,sql语句返回的数据列分别和各字段绑定。另一个更好的办法是采用组合 的方式,为Account定义一个user的属性,使之将和user有关的数据封装起来。 Mybatis中可以使用association
标签定义,IAccountDao.xml
的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <resultMap id ="accountUserMap" type ="account" > <id property ="id" column ="aid" > </id > <result property ="uid" column ="uid" > </result > <result property ="money" column ="money" > </result > <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 >
Account实体类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Account implements Serializable { ... private User user; public User getUser () { return user; } public void setUser (User user) { this .user = user; } }
一对多的collection标签 一个User可以对应多个Account,User对Account是1对多的关系。 在Mybatis中可以使用collection
标签定义,IUserDao.xml
的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <resultMap id ="userAccountMap" type ="user" > <id property ="id" column ="id" > </id > <result property ="username" column ="username" > </result > <result property ="address" column ="address" > </result > <result property ="sex" column ="sex" > </result > <result property ="birthday" column ="birthday" > </result > <collection property ="accounts" ofType ="account" > <id column ="aid" property ="id" > </id > <result column ="uid" property ="uid" > </result > <result column ="money" property ="money" > </result > </collection > </resultMap > <select id ="findAll" resultMap ="userAccountMap" > select u.*,a.id as aid ,a.uid,a.money from user u left outer join account a on u.id =a.uid</select >
User实体类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class User implements Serializable { ... private List<Account> accounts; public List<Account> getAccounts () { return accounts; } public void setAccounts (List<Account> accounts) { this .accounts = accounts; } }
延迟加载策略 延迟加载就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据,也称懒加载。
好处:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
坏处:因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。
使用 assocation 实现延迟加载 1.修改IAccountDao.xml
,将关联用户设置为延迟加载:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <resultMap id ="accountUserMap" type ="account" > <id property ="id" column ="id" > </id > <result property ="uid" column ="uid" > </result > <result property ="money" column ="money" > </result > <association property ="user" column ="uid" javaType ="user" select ="com.itheima.dao.IUserDao.findById" > </association > </resultMap > <select id ="findAll" resultMap ="accountUserMap" > select * from account</select >
2.修改IUserDao.xml
,添加查询单个用户的方法:
1 2 3 4 <select id ="findById" parameterType ="int" resultType ="user" > select * from user where id = #{uid}</select >
3.开启 Mybatis 的延迟加载策略,修改SqlMapConfig.xml
:
1 2 3 4 5 6 7 <settings > <setting name ="lazyLoadingEnabled" value ="true" /> <setting name ="aggressiveLazyLoading" value ="false" > </setting > </settings >
设置完毕后,当只查询Account而没用到User属性时,不会执行User对象的查询sql语句。
使用 collection 实现延迟加载 1.修改IUserDao.xml
,将关联账号设置为延迟加载:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <resultMap id ="userAccountMap" type ="user" > <id property ="id" column ="id" > </id > <result property ="username" column ="username" > </result > <result property ="address" column ="address" > </result > <result property ="sex" column ="sex" > </result > <result property ="birthday" column ="birthday" > </result > <collection property ="accounts" ofType ="account" select ="com.itheima.dao.IAccountDao.findAccountByUid" column ="id" > </collection > </resultMap > <select id ="findAll" resultMap ="userAccountMap" > select * from user</select >
2.修改IAccountDao.xml
,添加根据用户id查询账号的方法:
1 2 3 4 <select id ="findAccountByUid" resultType ="account" > select * from account where uid = #{uid}</select >
3.开启mybatis延迟加载全局开关
Mybatis缓存 一级缓存 一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就存在。 比如在同一个SqlSession
下对用户查询2次User user = userDao.findById(41);
,只会执行一次sql语句,2次返回的User对象是同一个对象。 一级缓存是 SqlSession 默认的缓存,当调用 SqlSession 的修改,添加,删除,commit(),close(),clearCache()等方法时,就会清空一级缓存。
第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。
如果 sqlSession 去执行 commit 操作(执行插入、更新、删除),清空 SqlSession 中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存中获取用户信息。
二级缓存 二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个SqlSession可以共用二级缓存,二级缓存是跨 SqlSession 的。
二级缓存的开启与关闭
1.在SqlMapConfig.xml
文件开启二级缓存
1 2 3 4 5 <settings > <setting name ="cacheEnabled" value ="true" /> </settings >
2.配置需要二级缓存的Mapper映射文件
1 2 3 4 5 6 7 8 9 10 <?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 >
3.配置 statement 上面的 useCache 属性
1 2 3 4 <select id ="findById" parameterType ="INT" resultType ="user" useCache ="true" > select * from user where id = #{uid}</select >
将IUserDao.xml
映射文件中的<select>
标签中设置useCache="true"
代表当前这个 statement 要使用二级缓存,如果不使用二级缓存可以设置为 false。注意:
在select的statement中useCache默认为true,所以针对每次查询都需要最新的数据sql,要设置成 useCache=false,禁用二级缓存。
二级缓存是将查询对象序列化后缓存起来的,当再次查询时不会执行sql,而是从缓存中反序列化成对象,所以当使用二级缓存时,所缓存的类一定要实现java.io.Serializable
接口,这样才可以使用序列化方式来保存对象。
注解开发 使用Mybatis提供的注解方式,就可以不用创建Mapper映射文件了。
mybatis常用注解说明
@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result 一起使用,封装多个结果集
@ResultMap:实现引用@Results 定义的封装
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
@SelectProvider: 实现动态 SQL 映射
@CacheNamespace:实现注解二级缓存的使用
注解实现基本CRUD 1.编写SqlMapConfig.xml
配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <?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 ="com.itheima.domain" > </package > </typeAliases > <environments default ="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 class ="com.itheima.dao.IUserDao" > </mapper > </mappers > </configuration >
2.使用注解方式开发持久层接口IUserDao
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 public interface IUserDao { @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=#{id} ") @ResultMap("userMap") User findById (Integer userId) ; @Insert("insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday})") @SelectKey(keyColumn="id",keyProperty="id",resultType=Integer.class,before = false, statement = { "select last_insert_id()" }) int saveUser (User user) ; @Update("update user set username=#{username},sex=#{sex},birthday=#{birthday},address=#{address} where id=#{id}") int updateUser (User user) ; @Delete("delete from user where id=#{uid} ") int deleteUser (Integer userId) ; @Select("select count(*) from user ") int findTotalUser () ; @Select("select * from user where username like '%${value}%' ") List<User> findUserByName (String username) ; }
Results
指定了id后,接下来的ResultMap
可以直接引用这个Results
,比如查询所有用户和查询单个用户的方法。
注解实现复杂关系映射 注解对应的xml映射字段:
注解
xml
说明
@Results
<resultMap>
可以使用单个@Result 注解,也可以使用@Result 集合<br>@Results({@Result(),@Result()})
或@Results(@Result())
@Result
<id>
或<result>
属性介绍:id 是否是主键字段column 数据库的列名property 需要装配的属性名one 需要使用的@One注解many 需要使用的@Many注解
@One
<assocation>
用来指定子查询返回单一对象,属性介绍:select 指定用来多表查询的 sqlmapperfetchType 查询方式,懒加载或立即加载,会覆盖全局的配置参数 lazyLoadingEnabled 使用格式:<br>@Result(column="",property="",one=@One(select=""))
@Many
<collection>
用来指定子查询返回对象集合,属性介绍: 映射文件中的javaType 一般为 ArrayList,注解中可以不定义 使用格式:<br>@Result(column="",property="",many=@Many(select=""))
使用@One
注解实现一对一关系映射
1 2 3 4 5 6 7 8 9 10 11 12 13 @Select("select * from account") @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)) }) List<Account> findAll () ;
使用@Many
注解实现一对多关系映射
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @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"), @Result(column="id",property="accounts",many=@Many(select="com.itheima.dao.IAccountDao.findByUid",fetchType=FetchType.LAZY)) }) List<User> findAll () ;
注解实现二级缓存 1.在SqlMapConfig.xml
中开启二级缓存支持
1 2 3 4 5 <settings > <setting name ="cacheEnabled" value ="true" /> </settings >
2.在持久层接口中使用注解配置二级缓存
1 2 3 4 @CacheNamespace(blocking=true) public interface IUserDao { ... }
参考资料:
传智播客SSM框架之MyBatis深入浅出Mybatis系列(五)Mybatis事务篇