1.JDBC操作数据库问题分析 JDBC程序的回顾 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 public class 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" , "562644" ); String sql = "select * from user where username = ?" ; preparedStatement = connection.prepareStatement(sql); preparedStatement.setString(1 , "王五" ); 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) { e.printStackTrace(); } } } } }
JDBC问题分析
数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
Sql 语句在代码中硬编码,造成代码不易维护,实际应用 sql 变化的可能较大,sql 变动需要改变 java代码。
使用 preparedStatement 向占有位符号传参数存在硬编码,因为 sql 语句的 where 条件不一定,可能多也可能少,修改 sql 还要修改代码,系统不易维护。
对结果集解析存在硬编码(查询列名),sql 变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成 pojo 对象解析比较方便。
2. MyBatis 框架快速入门 框架概述 MyBatis
是一个优秀的基于 java
的持久层框架,它内部封装了 JDBC
,使开发者只需要关注 SQL 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建 statement
等繁杂的过程。MyBatis
通过 xml 或注解的方式将要执行的各种 statement 配置起来,并通过 java 对象和 statement 中sql 的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回。 采用 ORM 思想解决了实体和数据库映射的问题,对 jdbc 进行了封装,屏蔽了 jdbc api 底层访问细节,使我们不用与 jdbc api 打交道,就可以完成对数据库的持久化操作。
入门案例准备 数据库 项目搭建 创建MyBatis配置文件 环境搭建注意事项 创建数据库和插入测试数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 DROP TABLE IF EXISTS `user `;CREATE TABLE `user ` ( `id` int (11 ) NOT NULL auto_increment, `username` varchar (32 ) NOT NULL COMMENT '用户名称' , `birthday` datetime default NULL COMMENT '生日' , `sex` char (1 ) default NULL COMMENT '性别' , `address` varchar (256 ) default NULL COMMENT '地址' , PRIMARY KEY (`id`) ) ENGINE= InnoDB DEFAULT CHARSET= utf8; insert into `user `(`id`,`username`,`birthday`,`sex`,`address`) values (41 ,'老王' ,'2018-02-27 17:47:08' ,'男' ,'北京' ),(42 ,'小二王' ,'2018-03-02 15:09:37' ,'女' ,'北京金燕龙' ),(43 ,'小二王' ,'2018-03-04 11:34:34' ,'女' ,'北京金燕龙' ),(45 ,'曾庆' ,'2018-03-04 12:04:06' ,'男' ,'北京金燕龙' ),(46 ,'老王' ,'2018-03-07 17:37:26' ,'男' ,'北京' ),(48 ,'小马宝莉' ,'2018-03-08 11:44:00' ,'女' ,'北京修正' );
1.创建Maven 工程,添加pom依赖坐标:
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 <?xml version="1.0" encoding="UTF-8"?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <groupId > org.example</groupId > <artifactId > QuickMyBatis</artifactId > <version > 1.0-SNAPSHOT</version > <packaging > jar</packaging > <dependencies > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis</artifactId > <version > 3.4.6</version > </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 > <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <version > 4.10</version > <scope > test</scope > </dependency > </dependencies > </project >
2.在resource
目录下创建名为log4j.properties
的日志输出配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # Set root category priority to INFO and its only appender to CONSOLE. #log4j.rootCategory=INFO, CONSOLE debug info warn error fatal log4j.rootCategory=debug, CONSOLE, LOGFILE # Set the enterprise logger category to FATAL and its only appender to CONSOLE. log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE # CONSOLE is set to be a ConsoleAppender using a PatternLayout. log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15. 15t] %-5p %30. 30c %x - %m\n # LOGFILE is set to be a File appender using a PatternLayout. log4j.appender.LOGFILE=org.apache.log4j.FileAppender log4j.appender.LOGFILE.File=d:\axis.log log4j.appender.LOGFILE.Append=true log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15. 15t] %-5p %30. 30c %x - %m\n
3.创建名为domain
包,在该包下创建实体对象User.class
1 2 3 4 5 6 7 8 public class User implements Serializable { private Integer id; private String username; private Date birthday; private String sex; private String address; }
4.创建名为dao
包,在该包下创建接口类
1 2 3 4 5 6 7 8 9 10 11 12 public interface IUserDao { List<User> findAll () ; }
1.在resource
目录下创建包路径com.mybatis.dao
(一次只创建一个包)。创建名为IUserDao.xml
的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.mybatis.dao.IUserDao" > <select id ="findAll" resultType ="com.mybatis.domain.User" > select * from user </select > </mapper >
2.在resource
目录下创建Mybatis配置文件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 <?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/eesy_mybatis?characterEncoding=utf8" /> <property name ="username" value ="root" /> <property name ="password" value ="1234" /> </dataSource > </environment > </environments > <mappers > <mapper resource ="com/mybatis/dao/IUserDao.xml" /> </mappers > </configuration >
第一点:
在Mybatis中它把持久层的操作接口名称和映射文件也叫做:Mapper
所以:IUserDao 和 IUserMapper是一样的
第二点:在IDEA中创建目录的时候,它和包是不一样的
包在创建时:com.mybatis.dao它是三级 结构
目录在创建时:com.mybatis.dao是一级 目录
第三点:Mybatis的映射配置文件位置必须和dao接口的包结构相同 第四点:映射配置文件的mapper标签namespace属性的取值必须是dao接口的全限定类名 第五点:映射配置文件的操作配置(select),id属性的取值必须是dao接口的方法名
当我们遵从了第三,四,五点之后,我们在开发中就无须再写dao的实现类。
入门案例 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 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(); } }
2.运行测试类会输出查询的结果:
1 2 3 4 5 6 User{id=41 , username='老王' , birthday=Tue Feb 27 17 :47 :08 CST 2018 , sex='男' , address='北京' } User{id=42 , username='小二王' , birthday=Fri Mar 02 15 :09:37 CST 2018 , sex='女' , address='北京金燕龙' } User{id=43 , username='小二王' , birthday=Sun Mar 04 11 :34 :34 CST 2018 , sex='女' , address='北京金燕龙' } User{id=45 , username='曾庆' , birthday=Sun Mar 04 12 :04 :06 CST 2018 , sex='男' , address='北京金燕龙' } User{id=46 , username='老王' , birthday=Wed Mar 07 17 :37 :26 CST 2018 , sex='男' , address='北京' } User{id=48 , username='小马宝莉' , birthday=Thu Mar 08 11 :44 :00 CST 2018 , sex='女' , address='北京修正' }
1.把IUserDao.xml移除,在dao接口的方法上使用@Select
注解,并且指定SQL语句.同时需要在SqlMapConfig.xml
中的mapper配置时,使用class属性指定dao接口的全限定类名。
1 2 3 4 5 6 <mappers > <mapper class ="com.mybatis.dao.IUserDao" /> </mappers >
2.修改接口方法,改成注解方式
1 2 3 4 5 6 7 8 9 10 11 12 13 public interface IUserDao { @Select("select * from user") List<User> findAll () ; }
3.运行测试类会输出查询的结果:
1 2 3 4 5 6 User{id=41 , username='老王' , birthday=Tue Feb 27 17 :47 :08 CST 2018 , sex='男' , address='北京' } User{id=42 , username='小二王' , birthday=Fri Mar 02 15 :09:37 CST 2018 , sex='女' , address='北京金燕龙' } User{id=43 , username='小二王' , birthday=Sun Mar 04 11 :34 :34 CST 2018 , sex='女' , address='北京金燕龙' } User{id=45 , username='曾庆' , birthday=Sun Mar 04 12 :04 :06 CST 2018 , sex='男' , address='北京金燕龙' } User{id=46 , username='老王' , birthday=Wed Mar 07 17 :37 :26 CST 2018 , sex='男' , address='北京' } User{id=48 , username='小马宝莉' , birthday=Thu Mar 08 11 :44 :00 CST 2018 , sex='女' , address='北京修正' }
我们在实际开发中,都是越简便越好,所以都是采用不写dao实现类的方式。不管使用XML还是注解配置。但是Mybatis它是支持写dao实现类的。
1.在Dao
目录下创建包impl
,创建Dao的实现类,编写查询方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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.mybatis.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 20 21 22 23 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(); } }
入门案例中设计模式分析
3.基于代理Dao实现CRUD操作
查询所有 保存 更新 删除 根据ID查询 模糊查询 聚合函数查询 获取保存数据的ID 1.创建名为dao
包,在该包下创建查询所有的接口类
1 2 3 4 5 6 7 8 9 10 11 12 public interface IUserDao { List<User> findAll () ; }
2.在resource
目录下,修改IUserDao.xml
的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.mybatis.dao.IUserDao" > <select id ="findAll" resultType ="com.mybatis.domain.User" > select * from user </select > </mapper >
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 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(); } }
4.执行测试类
1 2 3 4 5 6 User{id=41 , username='老王' , birthday=Tue Feb 27 17 :47 :08 CST 2018 , sex='男' , address='北京' } User{id=42 , username='小二王' , birthday=Fri Mar 02 15 :09:37 CST 2018 , sex='女' , address='北京金燕龙' } User{id=43 , username='小二王' , birthday=Sun Mar 04 11 :34 :34 CST 2018 , sex='女' , address='北京金燕龙' } User{id=45 , username='曾庆' , birthday=Sun Mar 04 12 :04 :06 CST 2018 , sex='男' , address='北京金燕龙' } User{id=46 , username='老王' , birthday=Wed Mar 07 17 :37 :26 CST 2018 , sex='男' , address='北京' } User{id=48 , username='小马宝莉' , birthday=Thu Mar 08 11 :44 :00 CST 2018 , sex='女' , address='北京修正' }
1.在Dao
包下,IUserDao
接口中创建保存的接口类
1 2 3 4 5 void saveUser (User user) ;
2.在IUserDao.xml
Mapper映射文件中,编写保存的配置方法
1 2 3 4 <insert id ="saveUser" parameterType ="com.mybatis.domain.User" > insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday}); </insert >
3.在编写测试类的时候,我们发现每写一个测试方法,我们都要读取配置文件、创建SqlSessionFactory工厂,获取对象、关闭资源。麻烦臃肿,我们来优化一下代码: 测试类:
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 testSave () { User user = new User(); user.setUsername("modify User property" ); user.setAddress("北京市顺义区" ); user.setSex("男" ); user.setBirthday(new Date()); userDao.saveUser(user); } }
1.在Dao
包下,IUserDao
接口中创建更新的接口类
1 2 3 4 5 void updateUser (User user) ;
2.在IUserDao.xml
Mapper映射文件中,编写更新的配置方法
1 2 3 4 <update id ="updateUser" parameterType ="com.mybatis.domain.User" > update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id} </update >
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 44 45 46 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 testUpdate () { User user = new User(); user.setId(50 ); user.setUsername("mybastis update user" ); user.setAddress("北京市顺义区" ); user.setSex("女" ); user.setBirthday(new Date()); userDao.updateUser(user); } }
1.在Dao
包下,IUserDao
接口中创建根据ID删除的接口类
1 2 3 4 5 void deleteUser (Integer userId) ;
2.在IUserDao.xml
Mapper映射文件中,编写删除的配置方法
1 2 3 4 5 <delete id ="deleteUser" parameterType ="java.lang.Integer" > delete from user where id = #{uid} </delete >
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 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 testDelete () { userDao.deleteUser(49 ); } }
1.在Dao
包下,IUserDao
接口中创建根据ID查询的接口类
1 2 3 4 5 6 User findById (Integer userId) ;
2.在IUserDao.xml
Mapper映射文件中,编写对应Sql
1 2 3 4 <select id ="findById" parameterType ="INT" resultType ="com.mybatis.domain.User" > select * from user where id = #{uid} </select >
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 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(48 ); System.out.println(user); } }
1.在Dao
包下,IUserDao
接口中创建模糊查询的接口类
1 2 3 4 5 6 List<User> findByName (String username) ;
2.在IUserDao.xml
Mapper映射文件中,编写对应Sql
1 2 3 4 5 <select id ="findByName" parameterType ="string" resultType ="com.mybatis.domain.User" > select * from user where username like #{name} </select >
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 44 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 testFindByName () { List<User> users = userDao.findByName("%小%" ); for (User user : users){ System.out.println(user); } } }
我们在配置文件中没有加入%
来作为模糊查询的条件,所以在传入字符串实参时,就需要给定模糊查询的标识%
。配置文件中的#{username}
也只是一个占位符,所以 SQL 语句显示为“?”
。
模糊查询的另一种配置方式
第一步:修改 SQL 语句的配置,配置如下:
1 2 3 4 <select id ="findByName" parameterType ="string" resultType ="com.mybatis.domain.User" > select * from user where username like '%${value}%' </select >
我们在上面将原来的#{} 占位符,改成了${value} 。注意如果用模糊查询的这种写法,那么${value} 的写法就是固定的,不能写成其它名字。
第二步:测试,如下:
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 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 testFindByName () { List<User> users = userDao.findByName("王" ); for (User user : users){ System.out.println(user); } } }
#{}与${}的区别 #{}表示一个占位符号 通过#{}可以实现 preparedStatement 向占位符中设置值,自动进行 java 类型和 jdbc 类型转换,#{}可以有效防止 sql 注入。 #{}可以接收简单类型值或 pojo 属性值。 如果 parameterType 传输单个简单类型值,#{}括号中可以是 value 或其它名称。${}表示拼接 sql 串 通过${}可以将 parameterType 传入的内容拼接在 sql 中且不进行 jdbc 类型转换, ${}可以接收简单类型值或 pojo 属性值,如果 parameterType 传输单个简单类型值,${}括号中只能是 value。
1.在Dao
包下,IUserDao
接口中添加查询总数方法
2.在IUserDao.xml
Mapper映射文件中,编写对应Sql
1 2 3 4 <select id ="findTotal" resultType ="int" > select count(id) from user; </select >
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 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 testFindTotal () { int count = userDao.findTotal(); System.out.println(count); } }
为了避免不必要的错误,请务必确认返回的值是否为自动增长,以及结合实际业务来考虑。
1.新增用户后,同时还要返回当前新增用户的 id 值,前提是 id 是由数据库的自动增长来实现的 ,所以就相当于我们要在新增后将自动增长 auto_increment
的值返回。
1 2 3 4 5 6 7 <insert id ="saveUser" parameterType ="com.mybatis.domain.User" > <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 >
2.测试方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Test public void testSave () { User user = new User(); user.setUsername("modify User property" ); user.setAddress("北京市顺义区" ); user.setSex("男" ); user.setBirthday(new Date()); System.out.println("保存前:" +user); int id=userDao.saveUser(user); System.out.println(user.getId()); System.out.println("保存后:" +user); }
4.Mybatis入参深入 SQL 语句传参,使用标签的 parameterType
属性来设定。该属性的取值可以是:
基本类型
引用类型(例如:String 类型)
实体类类型(POJO 类)
实体类的包装类
传递简单类型 传递POJO对象 传递 pojo 包装(VO)对象 基本类型 和String :
我们可以直接写类型名称,例如:parameterType=string
。
也可以使用包名. 类名的方式,例如 :java.lang.String
。
实体类类型 :目前我们只能使用全限定类名。 究其原因:是 Mybaits 在加载时已经把常用的数据类型注册了别名,从而我们在使用时可以不写包名,而我们的是实体类并没有注册别名,所以必须写全限定类名。
MyBatis使用OGNL 表达式解析对象字段的值,#{}
或者${}
括号中的值为pojo属性名称。
OGNL表达式:对象图导航语言(Object Graphic Navigation Language)
它是通过对象的取值方法来获取数据。在写法上把get给省略了。 比如:我们获取用户的名称
1 2 类中的写法:user.getUsername(); OGNL表达式写法:user.username
MyBatis中为什么能直接写username
,而不用user.
呢? 因为在parameterType
中已经提供了属性所属的类,所以此时不需要写对象名。
开发中通过 pojo 传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。 Vo 类中包含 pojo。 需求:根据用户名查询用户信息,查询条件放到 QueryVo 的 user 属性中。
编写QueryVo:
1 2 3 4 5 6 7 8 9 10 11 12 public class QueryVo { private User user; public User getUser () { return user; } public void setUser (User user) { this .user = user; } }
在Dao
包下,IUserDao
接口中添加queryVo查询方法
1 2 3 4 5 6 List<User> findUserByVo (QueryVo vo) ;
编写Mapper映射文件:
1 2 3 4 5 <select id ="findUserByVo" parameterType ="com.mybatis.domain.QueryVo" resultType ="com.mybatis.domain.User" > select * from user where username like #{user.username} </select >
测试方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Test public void testFindByVo () { QueryVo vo = new QueryVo(); User user = new User(); user.setUsername("%王%" ); vo.setUser(user); List<User> users = userDao.findUserByVo(vo); for (User u : users){ System.out.println(u); } }
5.Mybatis出参深入 resultType 配置结果类型 1.Dao接口
2.映射配置
1 2 3 4 <select id ="findTotal" resultType ="int" > select count(*) from user; </select >
1.Dao接口
1 2 3 4 5 List<User> findAll () ;
2.映射配置
1 2 3 4 5 <select id ="findAll" resultType ="com.mybatis.domain.User" > select * from user </select >
以前我们写的对象属性和数据库列名都是一致的,那么要不一致怎么办?
修改实体类对象User:
1 2 3 4 5 6 7 8 public class User implements Serializable { private Integer userId; private String userName; private String userAddress; private String userSex; private Date userBirthday; }
此时的实体类属性和数据库表的列名已经不一致了.
Dao接口方法:
1 2 3 4 5 List<User> findAll () ;
Mapper映射文件:
1 2 3 4 <select id ="findAll" resultType ="com.mybatis.domain.User" > select * from user </select >
我们再次执行findAll方法
:
1 2 3 4 5 6 7 8 9 10 11 @Test public void testFindAll () { List<User> users = userDao.findAll(); for (User user : users){ System.out.println(user); } }
输出结果:
1 2 3 4 5 6 ser{userId=null , userName='老王' , userAddress='null' , userSex='null' , userBirthday=null } User{userId=null , userName='小二王' , userAddress='null' , userSex='null' , userBirthday=null } User{userId=null , userName='小二王' , userAddress='null' , userSex='null' , userBirthday=null } User{userId=null , userName='曾庆' , userAddress='null' , userSex='null' , userBirthday=null } User{userId=null , userName='老王' , userAddress='null' , userSex='null' , userBirthday=null } User{userId=null , userName='小马宝莉' , userAddress='null' , userSex='null' , userBirthday=null }
为什么名称会有值呢?因为Mysql 在 windows 系统中不区分大小写!
那我们有没有办法解决这个问题呢? 修改映射配置可以解决这个问题,使用别名查询。
1 2 3 4 5 <select id ="findAll" resultType ="com.mybatis.domain.User" > select id as userId,username as userName,birthday as userBirthday, sex as userSex,address as userAddress from user </select >
这样数据库中别名和实体中别名保持了一致,运行后发现结果能正常显示了,证明可以封装到对象中,如果我们的查询很多,都使用别名的话写起来岂不是很麻烦,有没有别的解决办法呢?请看下一章节。
resultMap 配置结果类型 resultMap 标签可以建立查询的列名和实体类的属性名称不一致时建立对应关系 ,从而实现封装。 在 select 标签中使用 resultMap 属性指定引用即可。同时 resultMap 可以实现将查询结果映射为复杂类型的 pojo,比如在查询结果映射对象中包括 pojo 和 list 实现一对一查询和一对多查询。
1 2 3 4 5 6 7 8 9 10 <!-- 配置 查询结果的列名和实体类的属性名的对应关系 --> <resultMap id="userMap" type="com.mybatis.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>
id 标签 :用于指定主键字段result 标签:用于指定非主键字段column 属性:用于指定数据库列名property 属性:用于指定实体类属性名称
1 2 3 4 <select id ="findAll" resultMap ="userMap" > select * from user </select >
我们再次执行findAll方法
:
1 2 3 4 5 6 7 8 9 10 11 @Test public void testFindAll () { List<User> users = userDao.findAll(); for (User user : users){ System.out.println(user); } }
发现查询成功了。
6.SqlMapConfig.xml配置文件 内容和顺序 -properties(属性) –property
-settings(全局配置参数) –setting
-typeAliases(类型别名) –typeAliase –package -typeHandlers(类型处理器) -objectFactory(对象工厂) -plugins(插件)
-environments(环境集合属性对象) –environment(环境子属性对象) —transactionManager(事务管理) —dataSource(数据源)
-mappers(映射器) –mapper –package
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 <?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.mybatis.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 > <package name ="com.mybatis.dao" > </package > </mappers > </configuration >
properties(属性) 在使用 properties
标签配置时,我们可以采用两种方式指定属性配置。
第一种 :在标签内部配置连接数据库的信息
1 2 3 4 5 6 <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 >
那么,我们在下面就可以直接引用上面所配置了,方式: ${property name}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <environments default ="mysql" > <environment id ="mysql" > <transactionManager type ="JDBC" > </transactionManager > <dataSource type ="POOLED" > <property name ="driver" value ="${driver}" > </property > <property name ="url" value ="${url}" > </property > <property name ="username" value ="${username}" > </property > <property name ="password" value ="${password}" > </property > </dataSource > </environment > </environments >
肯定有人会说,这不是吃饱了撑的嘛,在下面写的好好的,非要写到上面去。其实这种写法是支持提取到外部的配置文件中,请看下面第二种方式
第二种 :也可以通过属性引用外部配置文件信息 在 resources
下定义 jdbcConfig.properties 文件
1 2 3 4 jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql: jdbc.username=root jdbc.password=1234
外部配置文件写好之后,我们需要在SqlMapConfig.xml
进行引用了。配置properties
有两个属性:resource属性 :常用的, 用于指定配置文件的位置,是按照类路径的写法来写,并且必须存在于类路径下。
1 2 3 4 5 6 <properties resource ="jdbcConfig.properties" > </properties >
同时,我们环境配置也需要修改,不能只写name,需要写jdbc.name
。因为我们在jdbcConfig.properties是以jdbc.name
开头设置的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <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 >
url属性: 是要求按照Url的写法来写地址。 URL:Uniform Resource Locator 统一资源定位符。它是可以唯一标识一个资源的位置。 写法:http://localhost:8080/mybatisserver/demo1Servlet URI:Uniform Resource Identifier 统一资源标识符。它是在应用中可以唯一定位一个资源的。
1 2 3 4 5 6 <properties url ="file:///D:/Svideo/jdbcConfig.properties" > </properties >
typeAliases(类型别名) Mybatis支持默认别名,比如xml中 parameterType
属性中的int
,Sting
想大写就大写想小写就小写。但是我们实体对象缺不能随便写。这是因为基本类型都配置默认别名。我们也可以采用自定义别名方式来开发。自定义别名 在 SqlMapConfig.xml 中配置:
1 2 3 4 5 6 7 8 <typeAliases > <package name ="com.mybatis.domain" > </package > </typeAliases >
mappers(映射器) **1.<package name=””/>**:注册指定包下的所有 mapper 接口如:
1 <package name ="com.mybatis.dao" />
示例:
1 2 3 4 5 6 <mappers > <package name ="com.mybatis.dao" > </package > </mappers >
注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。
**2.<mapper resource=” “ />**:使用相对于类路径的资源如:
1 <mapper resource ="com/mybatis/dao/IUserDao.xml" />
**3.<mapper class=” “ />**:使用 mapper 接口类路径如:
1 <mapper class ="com.mybatis.dao.UserDao" />
注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。
7.Mybatis 连接池与事务深入 连接池 :我们在实际开发中都会使用连接池。我们在实际开发中都会使用连接池。
Mybatis 的连接池技术 我们在前面的 WEB 课程中也学习过类似的连接池技术,而在 Mybatis 中也有连接池技术,但是它采用的是自己的连接池技术。在 Mybatis 的 qlMapConfig.xml
配置文件中,通过来实现 Mybatis 中连接池的配置。
mybatis连接池提供了3种方式的配置,配置的位置在主配置文件SqlMapConfig.xml
中的dataSource
标签。type
属性就是表示采用何种连接池方式。 type属性的取值:POOLED :采用传统的javax.sql.DataSource规范中的连接池,mybatis中有针对规范的实现UNPOOLED :采用传统的获取连接的方式,虽然也实现Javax.sql.DataSource接口,但是并没有使用池的思想。JNDI :采用服务器提供的JNDI技术实现,来获取DataSource对象,不同的服务器所能拿到DataSource是不一样。
注意: 如果不是web或者maven的war工程,是不能使用的。我们课程中使用的是tomcat服务器,采用连接池就是dbcp连接池。
Mybatis 的事务控制 什么是事务 :数据库事务是指作为单个逻辑单元执行的一系列操作,要么完全执行,玩么完全地不执行。
事务的四大特性 ACID:原子性、一致性、隔离性和持久性。
原子性(Atomicity):一个原子事务要么完整执行,要么干脆不执行。这意味着,工作单元中的每项任务都必须正确执行。如果有任一任务执行失败,则整个工作单元或事务就会被终止。即此前对数据所作的任何修改都将被撤销。如果所有任务都被成功执行,事务就会被提交,即对数据所作的修改将会是永久性的。
一致性(Consistency): 一致性代表了底层数据存储的完整性。它必须由事务系统和应用开发人员共同来保证。事务系统通过保证事务的原子性,隔离性和持久性来满足这一要求; 应用开发人员则需要保证数据库有适当的约束(主键,引用完整性等),并且工作单元中所实现的业务逻辑不会导致数据的不一致(即,数据预期所表达的现实业务情况不相一致)。例如,在一次转账过程中,从某一账户中扣除的金额必须与另一账户中存入的金额相等。
隔离性(Isolation):隔离性意味着事务必须在不干扰其他进程或事务的前提下独立执行。换言之,在事务或工作单元执行完毕之前,其所访问的数据不能受系统其他部分的影响。
持久性(Durability):持久性表示在某个事务的执行过程中,对数据所作的所有改动都必须在事务成功结束前保存至某种物理存储设备。这样可以保证,所作的修改在任何系统瘫痪时不至于丢失。
不考虑隔离性会产生的3个问题:解决办法四种隔离级别
数据库事务的隔离级别有4种,由低到高分别为Read uncommitted 、Read committed 、Repeatable read 、Serializable 。而且,在事务的并发操作中可能会出现脏读,不可重复读,幻读。下面通过事例一一阐述它们的概念与联系。
Read uncommitted :读未提交,顾名思义,就是一个事务可以读取另一个未提交事务的数据。 事例:老板要给程序员发工资,程序员的工资是3.6万/月。但是发工资时老板不小心按错了数字,按成3.9万/月,该钱已经打到程序员的户口,但是事务还没有提交,就在这时,程序员去查看自己这个月的工资,发现比往常多了3千元,以为涨工资了非常高兴。但是老板及时发现了不对,马上回滚差点就提交了的事务,将数字改成3.6万再提交。 分析:实际程序员这个月的工资还是3.6万,但是程序员看到的是3.9万。他看到的是老板还没提交事务时的数据。这就是脏读。
那怎么解决脏读呢?Read committed!读提交,能解决脏读问题。
Read committed :读提交,顾名思义,就是一个事务要等另一个事务提交后才能读取数据。 事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(程序员事务开启),收费系统事先检测到他的卡里有3.6万,就在这个时候!!程序员的妻子要把钱全部转出充当家用,并提交。当收费系统准备扣款时,再检测卡里的金额,发现已经没钱了(第二次检测金额当然要等待妻子转出金额事务提交完 )。程序员就会很郁闷,明明卡里是有钱的… 分析:这就是读提交,若有事务对数据进行更新(UPDATE)操作时,读操作事务要等待这个更新操作事务提交后才能读取数据,可以解决脏读问题。但在这个事例中,出现了一个事务范围内两个相同的查询却返回了不同数据,这就是不可重复读 。
那怎么解决可能的不可重复读问题?Repeatable read !
Repeatable read :重复读,就是在开始读取数据(事务开启)时,不再允许修改操作 事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(事务开启,不允许其他事务的UPDATE修改操作 ),收费系统事先检测到他的卡里有3.6万。这个时候他的妻子不能转出金额了。接下来收费系统就可以扣款了。 分析:重复读可以解决不可重复读问题。写到这里,应该明白的一点就是,不可重复读对应的是修改,即UPDATE操作 。但是可能还会有幻读 问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作 。
什么时候会出现幻读? 事例:程序员某一天去消费,花了2千元,然后他的妻子去查看他今天的消费记录(全表扫描FTS,妻子事务开启),看到确实是花了2千元,就在这个时候,程序员花了1万买了一部电脑,即新增INSERT 了一条消费记录,并提交。当妻子打印程序员的消费记录清单时(妻子事务提交),发现花了1.2万元,似乎出现了幻觉,这就是幻读。
那怎么解决幻读问题?Serializable!
Serializable 序列化 :Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行 ,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。
值得一提的是:大多数数据库默认的事务隔离级别是Read committed,比如Sql Server , Oracle。Mysql的默认隔离级别是Repeatable read。
8.Mybatis 的动态 SQL 语句 Mybatis 的映射文件中,前面我们的 SQL 都是比较简单的,有些时候业务逻辑复杂时,我们的 SQL 是动态变化的,此时在前面的学习中我们的 SQL 就不能满足要求了。
动态 SQL 之<if>标签 我们根据实体类的不同取值,使用不同的 SQL 语句来进行查询。比如在 id 如果不为空时可以根据 id 查询,如果 username 不同空时还要加入用户名作为条件。这种情况在我们的多条件组合查询中经常会碰到。
编写持久层Dao接口
1 2 3 4 5 6 List<User> findUserByCondition (User user) ;
持久层 Dao 映射配置:注:这里已经配置 查询结果的列名和实体类的属性名的对应关系-userMap(配置了别名)
1 2 3 4 5 6 7 8 9 <select id ="findUserByCondition" resultMap ="userMap" parameterType ="user" > select * from user where 1=1 <if test ="userName != null" > and username = #{userName} </if > <if test ="userSex != null" > and sex = #{userSex} </if > </select >
注:凡是sql语句中的属性名无关大小写,但是判断条件是需要大小写的,与实体属性名保持一致。如判断条件和中括号中的属性名。 这种方式有风险,如果一旦条件都不匹配会出现批量查询或者批量删除。应尽量避免此种方式。
动态 SQL 之<where>标签 为了简化上面 where 1=1 的条件拼装,我们可以采用<where>
标签来简化开发。
持久层 Dao 映射配置
1 2 3 4 5 6 7 8 9 10 11 <select id ="findUserByCondition" resultMap ="userMap" parameterType ="user" > select * from user <where > <if test ="userName != null" > and username = #{userName} </if > <if test ="userSex != null" > and sex = #{userSex} </if > </where > </select >
动态标签之<foreach>标签 传入多个 id 查询用户信息,用下边两个 sql 实现:
1 2 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 )
这样我们在进行范围查询时,就要将一个集合中的值,作为参数动态添加进来。 这样我们将如何进行参数的传递?
在 QueryVo 中加入一个 List 集合用于封装参数
1 2 3 4 5 6 7 public class QueryVo { private User user; private List<Integer> ids;
持久层Dao接口
1 2 3 4 5 6 List<User> findUserInIds (QueryVo vo) ;
持久层 Dao 映射配置
1 2 3 4 <sql id ="defaultUser" > select * from user </sql >
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
:代表分隔符
测试方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Test public void testFindInIds () { QueryVo vo = new QueryVo(); List<Integer> ids = new ArrayList<Integer>(); ids.add(41 ); ids.add(42 ); ids.add(43 ); ids.add(46 ); ids.add(57 ); vo.setIds(ids); List<User> users = userDao.findInIds(vo); for (User user : users) { System.out.println(user); } }
抽取重复的sql语句 1 2 3 4 <sql id ="defaultUser" > select * from user </sql >
引用:
1 <include refid ="defaultUser" > </include >
9.Mybatis 多表查询之一对多 mybatis中的多表查询: 示例:用户和账户 一个用户可以有多个账户 一个账户只能属于一个用户(多个账户也可以属于同一个用户) 步骤: 1、建立两张表:用户表,账户表,让用户表和账户表之间具备一对多的关系:需要使用外键在账户表中添加 2、建立两个实体类:用户实体类和账户实体类,让用户和账户的实体类能体现出来一对多的关系 3、建立两个配置文件,用户的配置文件和账户的配置文件 4、实现配置: 当我们查询用户时,可以同时得到用户下所包含的账户信息 当我们查询账户时,可以同时得到账户的所属用户信息
Account数据库脚本
1 2 3 4 5 6 7 8 9 10 11 12 DROP TABLE IF EXISTS `account`;CREATE TABLE `account` ( `ID` int (11 ) NOT NULL COMMENT '编号' , `UID` int (11 ) default NULL COMMENT '用户编号' , `MONEY` double default NULL COMMENT '金额' , PRIMARY KEY (`ID`), KEY `FK_Reference_8` (`UID`), CONSTRAINT `FK_Reference_8` FOREIGN KEY (`UID`) REFERENCES `user ` (`id`) ) ENGINE= InnoDB DEFAULT CHARSET= utf8; insert into `account`(`ID`,`UID`,`MONEY`) values (1 ,46 ,1000 ),(2 ,45 ,1000 ),(3 ,46 ,2000 );
User数据库脚本
1 2 3 4 5 6 7 8 9 DROP TABLE IF EXISTS `user `;CREATE TABLE `user ` ( `id` int (11 ) NOT NULL AUTO_INCREMENT, `username` varchar (32 ) NOT NULL COMMENT '用户名称' , `birthday` datetime DEFAULT NULL COMMENT '生日' , `sex` char (1 ) DEFAULT NULL COMMENT '性别' , `address` varchar (256 ) DEFAULT NULL COMMENT '地址' , PRIMARY KEY (`id`) ) ENGINE= InnoDB AUTO_INCREMENT= 57 DEFAULT CHARSET= utf8;
一对一查询(多对一) Account子类方式查询 建立实体类关系关系的方式查询 需求:查询所有账户信息,关联查询下单用户信息。 注意:因为一个账户信息只能供某个用户使用,所以从查询账户信息出发关联查询用户信息为一对一查询。如果从用户信息出发查询用户下的账户信息则为一对多查询,因为一个用户可以有多个账户。
1.定义Account实体对象
1 2 3 4 5 6 7 public class Account implements Serializable { private Integer id; private Integer uid; private Double money; }
2.编写IAccountDao映射文件
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.mybatis.dao.IAccountDao" > <select id ="findAll" resultType ="account" > select * from account </select > </mapper >
3.创建数据库IAccountDao
接口
1 2 3 4 5 6 7 8 9 10 public interface IAccountDao { List<AccountUser> findAllAccount () ; }
那么,问题来了,我们想查询所有账户,同时还要获取到当前账户的所属用户信息,需要先把sql语句写出来。
1 SELECT u.* ,a.id as aid,a.uid,a.money from account a,user u where u.id= a.UID;
但是,目前Account类中是不能包含用户信息的,但是我们选择最直接最简单的方式,去创建一个AccountUser
类,包含User
对象的两个属性,并且继承父类Account
。这样就可以查询到User对象的用户名和地址。也可以查询到Account的所有信息。
创建AccountUser类
:
1 2 3 4 5 6 7 8 9 10 11 12 13 public class AccountUser extends Account { private String username; private String address; @Override public String toString () { return super .toString()+" AccountUser{" + "username='" + username + '\'' + ", address='" + address + '\'' + '}' ; } }
持久层IAccountDao.xml
映射配置
1 2 3 4 5 <select id ="findAllAccount" resultType ="accountuser" > select a.*,u.username,u.address from account a , user u where u.id = a.uid; </select >
定义专门的 pojo 类作为输出类型,其中定义了 sql 查询结果集所有的字段。此方法较为简单,企业中使用普遍。(说白了就是缺什么补什么)
顾名思义就是让用户和账户的实体类能体现出来一对多的关系,也就是从表实体
应该包含一个主表实体
的对象引用。 通过面向对象的(has a)关系可以得知,我们可以在 Account
类中加入一个 User
类的对象来代表这个账户是哪个用户的。
1.修改Account类
1 2 3 4 5 6 7 8 9 10 11 12 public class Account implements Serializable { private Integer id; private Integer uid; private Double money; private User user; }
以前mapper定义的resultType
类型是Account
,但是我们增加了主表实体对象引用,所以显然不能满足需求。我们需调定义封装account和user的resultMap映射。 2.修改IAccountDao.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 <?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.mybatis.dao.IAccountDao" > <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 > </mapper >
注:resultMap
类型必须写成上边定义的accountUserMap
,否则不能正常封装数据。
一对多查询 需求:查询所有用户信息及用户关联的账户信息。 分析:用户信息和他的账户信息为一对多关系,并且查询过程中如果用户没有账户信息,此时也要将用户信息查询出来,我们想到了左外连接
查询比较合适。
编写sql
1 SELECT u.* , a.id id,a.uid, a.money FROM user u LEFT JOIN account a ON u.id = a.uid
修改User对象,在User对象中增加从表的集合引用
1 2 3 4 5 6 7 8 9 10 11 12 13 public class User implements Serializable { private Integer id; private String username; private String address; private String sex; private Date birthday; private List<Account> accounts; }
创建数据库IUserDao
接口
1 2 3 4 5 List<User> findAll () ;
编写IUserDao.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 mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.mybatis.dao.IUserDao" > <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 * from user u left outer join account a on u.id = a.uid </select > </mapper >
我们执行测试,会发现所有用户信息全部查出来,同时如果有账户信息的也会显示出来。
10.Mybatis 多表查询之多对多 mybatis中的多表查询: 示例:用户和角色 一个用户可以有多个角色 一个角色可以赋予多个用户 步骤: 1、建立两张表:用户表,角色表,让用户表和角色表具有多对多的关系。需要使用中间表,中间表中包含各自的主键,在中间表中是外键 。 2、建立两个实体类:用户实体类和角色实体类,让用户和角色的实体类能体现出来多对多的关系,各自包含对方一个集合引用 3、建立两个配置文件,用户的配置文件和角色的配置文件 4、实现配置: 当我们查询用户时,可以同时得到用户所包含的角色信息 当我们查询角色时,可以同时得到角色的所赋予的用户信息
角色Role数据库脚本:
1 2 3 4 5 6 7 8 9 DROP TABLE IF EXISTS `role`;CREATE TABLE `role` ( `ID` int (11 ) NOT NULL COMMENT '编号' , `ROLE_NAME` varchar (30 ) default NULL COMMENT '角色名称' , `ROLE_DESC` varchar (60 ) default NULL COMMENT '角色描述' , PRIMARY KEY (`ID`) ) ENGINE= InnoDB DEFAULT CHARSET= utf8; insert into `role`(`ID`,`ROLE_NAME`,`ROLE_DESC`) values (1 ,'院长' ,'管理整个学院' ),(2 ,'总裁' ,'管理整个公司' ),(3 ,'校长' ,'管理整个学校' );
中间表user_role数据库脚本:
1 2 3 4 5 6 7 8 9 10 11 12 DROP TABLE IF EXISTS `user_role`;CREATE TABLE `user_role` ( `UID` int (11 ) NOT NULL COMMENT '用户编号' , `RID` int (11 ) NOT NULL COMMENT '角色编号' , PRIMARY KEY (`UID`,`RID`), KEY `FK_Reference_10` (`RID`), CONSTRAINT `FK_Reference_10` FOREIGN KEY (`RID`) REFERENCES `role` (`ID`), CONSTRAINT `FK_Reference_9` FOREIGN KEY (`UID`) REFERENCES `user ` (`id`) ) ENGINE= InnoDB DEFAULT CHARSET= utf8; insert into `user_role`(`UID`,`RID`) values (41 ,1 ),(45 ,1 ),(41 ,2 );
实现 Role 到 User 的多对多 通过前面的学习,我们使用 Mybatis 实现一对多关系的维护。多对多关系其实我们看成是双向的一对多关系。
创建Role
实体对象:
1 2 3 4 5 6 7 8 9 10 11 12 public class User implements Serializable { private Integer id; private String username; private String address; private String sex; private Date birthday; private List<Role> roles; }
创建数据库Dao接口IRoleDao
1 2 3 4 5 6 7 8 public interface IRoleDao { List<Role> findAll () ; }
分析对应关系:
编写对应的sql
1 2 3 select u.* ,r.id as rid,r.role_name,r.role_desc from role rleft outer join user_role ur on r.id = ur.ridleft outer join user u on u.id = ur.uid
创建IRoleDao.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 <?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.mybatis.dao.IRoleDao" > <resultMap id ="roleMap" type ="role" > <id property ="roleId" column ="rid" > </id > <result property ="roleName" column ="role_name" > </result > <result property ="roleDesc" column ="role_desc" > </result > <collection property ="users" ofType ="user" > <id column ="id" property ="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 > </collection > </resultMap > <select id ="findAll" resultMap ="roleMap" > select u.*,r.id as rid,r.role_name,r.role_desc from role r left outer join user_role ur on r.id = ur.rid left outer join user u on u.id = ur.uid </select > </mapper >
查询角色并获取角色下用户信息到这里就完成了。
实现 User 到 Role 的多对多 其实和上边原理差不多,正好相反,只需修改对应sql即可 修改数据库Dao接口IUserDao
1 2 3 4 5 6 7 8 public interface IUserDao { List<User> findAll () ; }
修改User
实体对象
1 2 3 4 5 6 7 8 9 10 11 12 public class User implements Serializable { private Integer id; private String username; private String address; private String sex; private Date birthday; private List<Role> roles; }
创建IUserDao.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 <?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.mybatis.dao.IUserDao" > <resultMap id ="userMap" 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 ="roles" ofType ="role" > <id property ="roleId" column ="rid" > </id > <result property ="roleName" column ="role_name" > </result > <result property ="roleDesc" column ="role_desc" > </result > </collection > </resultMap > <select id ="findAll" resultMap ="userMap" > select u.*,r.id as rid,r.role_name,r.role_desc from user u left outer join user_role ur on u.id = ur.uid left outer join role r on r.id = ur.rid </select > </mapper >
11.JNDI数据源概述和原理 JNDI:Java Naming and Directory Interface。是SUN公司推出的一套规范,属于JavaEE技术之一。目的是模仿windows系统中的注册表。
创建一个Maven
工程,并导入坐标
在pom.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 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 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 <?xml version="1.0" encoding="UTF-8"?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <groupId > com.mybatis</groupId > <artifactId > day03_eesy_05jndi</artifactId > <version > 1.0-SNAPSHOT</version > <packaging > war</packaging > <name > day03_eesy_05jndi Maven Webapp</name > <url > http://www.example.com</url > <properties > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > <maven.compiler.source > 1.7</maven.compiler.source > <maven.compiler.target > 1.7</maven.compiler.target > </properties > <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 > </dependency > <dependency > <groupId > log4j</groupId > <artifactId > log4j</artifactId > <version > 1.2.12</version > </dependency > <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <version > 4.10</version > </dependency > <dependency > <groupId > javax.servlet</groupId > <artifactId > servlet-api</artifactId > <version > 2.5</version > </dependency > <dependency > <groupId > javax.servlet.jsp</groupId > <artifactId > jsp-api</artifactId > <version > 2.0</version > </dependency > </dependencies > <build > <finalName > day03_eesy_05jndi</finalName > <pluginManagement > <plugins > <plugin > <artifactId > maven-clean-plugin</artifactId > <version > 3.0.0</version > </plugin > <plugin > <artifactId > maven-resources-plugin</artifactId > <version > 3.0.2</version > </plugin > <plugin > <artifactId > maven-compiler-plugin</artifactId > <version > 3.7.0</version > </plugin > <plugin > <artifactId > maven-surefire-plugin</artifactId > <version > 2.20.1</version > </plugin > <plugin > <artifactId > maven-war-plugin</artifactId > <version > 3.2.0</version > </plugin > <plugin > <artifactId > maven-install-plugin</artifactId > <version > 2.5.2</version > </plugin > <plugin > <artifactId > maven-deploy-plugin</artifactId > <version > 2.8.2</version > </plugin > </plugins > </pluginManagement > </build > </project >
创建数据库Dao接口IUserDao
1 2 3 4 5 6 7 8 9 public interface IUserDao { List<User> findAll () ; }
创建实体对象User
1 2 3 4 5 6 7 8 9 10 public class User implements Serializable { private Integer userId; private String userName; private String userAddress; private String userSex; private Date userBirthday; }
在resources
目录下车创建IUserDao.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 <?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.mybatis.dao.IUserDao" > <resultMap id ="userMap" type ="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 > <select id ="findAll" resultMap ="userMap" > select * from user; </select > </mapper >
创建log4j.properties
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # Set root category priority to INFO and its only appender to CONSOLE. #log4j.rootCategory=INFO, CONSOLE debug info warn error fatal log4j.rootCategory=debug, CONSOLE, LOGFILE # Set the enterprise logger category to FATAL and its only appender to CONSOLE. log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE # CONSOLE is set to be a ConsoleAppender using a PatternLayout. log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15. 15t] %-5p %30. 30c %x - %m\n # LOGFILE is set to be a File appender using a PatternLayout. log4j.appender.LOGFILE=org.apache.log4j.FileAppender log4j.appender.LOGFILE.File=d:\axis.log log4j.appender.LOGFILE.Append=true log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15. 15t] %-5p %30. 30c %x - %m\n
创建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 <?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 > <typeAliases > <package name ="com.mybatis.domain" > </package > </typeAliases > <environments default ="mysql" > <environment id ="mysql" > <transactionManager type ="JDBC" > </transactionManager > <dataSource type ="JNDI" > <property name ="data_source" value ="java:comp/env/jdbc/eesy_mybatis" /> </dataSource > </environment > </environments > <mappers > <mapper resource ="com/mybatis/dao/IUserDao.xml" /> </mappers > </configuration >
在webapp
文件下创建META-INF
目录,在META-INF
目录中建立一个名为context.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 <?xml version="1.0" encoding="UTF-8"?> <Context > <Resource name ="jdbc/eesy_mybatis" type ="javax.sql.DataSource" auth ="Container" maxActive ="20" maxWait ="10000" maxIdle ="5" username ="root" password ="1234" driverClassName ="com.mysql.jdbc.Driver" url ="jdbc:mysql://localhost:3306/Mybatis" /> </Context >
在webapp
文件下创建index.jsp
文件
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 <%@ page import ="java.io.InputStream" %> <%@ page import ="org.apache.ibatis.io.Resources" %> <%@ page import ="org.apache.ibatis.session.SqlSessionFactoryBuilder" %> <%@ page import ="org.apache.ibatis.session.SqlSessionFactory" %> <%@ page import ="org.apache.ibatis.session.SqlSession" %> <%@ page import ="com.mybatis.dao.IUserDao" %> <%@ page import ="com.mybatis.domain.User" %> <%@ page import ="java.util.List" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <html> <body> <h2>Hello World!</h2> <% InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml" ); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(in); SqlSession sqlSession = factory.openSession(); IUserDao userDao = sqlSession.getMapper(IUserDao.class); List<User> users = userDao.findAll(); for (User user : users){ System.out.println(user); } sqlSession.close(); in.close(); %> </body> </html>
注:必须通过Tomcat方式才可以访问,单元测试不可以。Tomcat启动才会加载对应配置,如不启动,则无法找到。
12.Mybatis 延迟加载策略 通过前面的学习,我们已经掌握了 Mybatis 中一对一,一对多,多对多关系的配置及实现,可以实现对象的关联查询。实际开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的账户信息。此时就是我们所说的延迟加载。
立即加载 不管用不用,只要一调用方法,马上发起查询。
延迟加载 延迟加载: 就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载。
好处 :先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。坏处 :因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。
延迟加载示例 问题:在一对多中,当我们有一个用户,它有100个账户。 在查询用户的时候,要不要把关联的账户查出来? 在查询用户时,用户下的账户信息应该是,什么时候使用,什么时候查询的。 在查询账户的时候,要不要把关联的用户查出来? 在查询账户时,账户的所属用户信息应该是随着账户查询时一起查询出来。
在对应的四种表关系中:一对多,多对一,一对一,多对多 一对多,多对多:通常情况下我们都是采用延迟加载。 多对一,一对一:通常情况下我们都是采用立即加载。 我们使用了resultMap
来实现一对一,一对多,多对多关系的操作。主要是通过 association
、collection
实现一对一及一对多映射。association
、collection
具备延迟加载功能。
assocation 实现延迟加载(一对一) Collection 实现延迟加载(一对多) 1.创建数据库Dao层接口IAccountDao
1 2 3 4 5 6 7 8 public interface IAccountDao { List<Account> findAll () ; }
2.创建数据库Dao层接口IUserDao
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public interface IUserDao { List<User> findAll () ; User findById (Integer userId) ; }
3.创建Account
实体
1 2 3 4 5 6 7 8 9 10 public class Account implements Serializable { private Integer id; private Integer uid; private Double money; private User user; }
4.创建Account
实体
1 2 3 4 5 6 7 8 9 10 11 12 13 public class User implements Serializable { private Integer id; private String username; private String address; private String sex; private Date birthday; private List<Account> accounts; }
5.编写IUserDao.xml
Mapper映射文件
1 2 3 4 5 6 7 8 9 10 11 <?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.mybatis.dao.IUserDao" > <select id ="findById" parameterType ="INT" resultType ="user" > select * from user where id = #{uid} </select > </mapper >
6.编写IAccountDao.xml
Mapper映射文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?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.mybatis.dao.IAccountDao" > <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.mybatis.dao.IUserDao.findById" > </association > </resultMap > <select id ="findAll" resultMap ="accountUserMap" > select * from account </select > </mapper >
执行测试方法:
1 2 3 4 5 6 7 8 9 10 11 12 @Test public void testFindAll () { List<Account> accounts = accountDao.findAll(); for (Account account : accounts){ System.out.println("--------每个account的信息------------" ); System.out.println(account); System.out.println(account.getUser()); } }
7.当我们执行测试类时,发现一次性执行了3条语句,并没有真正实现延迟加载。想要实现真正的延迟加载,需要增加一个配置。
在Mybatis官网 ,有如下说明:
8.打开SqlMapConfig.xml
配置文件:
1 2 3 4 5 6 <settings > <setting name ="lazyLoadingEnabled" value ="true" /> <setting name ="aggressiveLazyLoading" value ="false" > </setting > </settings >
9.完整的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 36 37 38 39 40 41 <?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 > <settings > <setting name ="lazyLoadingEnabled" value ="true" /> <setting name ="aggressiveLazyLoading" value ="false" > </setting > </settings > <typeAliases > <package name ="com.mybatis.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 > <package name ="com.mybatis.dao" > </package > </mappers > </configuration >
10.再次执行测试方法:
1 2 3 4 5 6 7 8 9 10 11 12 @Test public void testFindAll () { List<Account> accounts = accountDao.findAll(); for (Account account : accounts){ System.out.println("--------每个account的信息------------" ); System.out.println(account); System.out.println(account.getUser()); } }
11.当我执行测试方法时,发现和上次有些变化,未开启延迟加载是一次性输出,开启后是一个一个输出。
为什么还是执行了3条语句呢,因为我们在测试类里面输出了用户的信息,这样说明了有需要则会进行加载。
那我们把整个遍历都注释掉:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 执行测试方法: ```java @Test public void testFindAll () { List<Account> accounts = accountDao.findAll(); }
再次执行,发现就只会执行一条语句了。
同样我们也可以在一对多关系配置的<collection<结点中配置延迟加载策略。<collection<结点中也有 select 属性,column 属性。
因为我们延迟加载部分需要用用到根据id查询Account,所以新增根据id查询接口
1.修改数据库Dao层接口IAccountDao
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public interface IAccountDao { List<Account> findAll () ; List<Account> findAccountByUid (Integer uid) ; }
2.修改IAccountDao.xml
Mapper映射文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?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.mybatis.dao.IAccountDao" > <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.mybatis.dao.IUserDao.findById" > </association > </resultMap > <select id ="findAccountByUid" resultType ="account" > select * from account where uid = #{uid} </select > </mapper >
3.修改IUserDao.xml
Mapper映射文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?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.mybatis.dao.IUserDao" > <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.mybatis.dao.IAccountDao.findAccountByUid" column ="id" > </collection > </resultMap > <select id ="findAll" resultMap ="userAccountMap" > select * from user </select > </mapper >
编写测试类:
1 2 3 4 5 6 7 8 9 10 11 public void testFindAll () { List<User> users = userDao.findAll(); }
再次执行,我们发现已经实现了延迟加载。延迟加载的思想就是,在用到的时候,去调用对方映射文件的一个配置来实现查询功能。
13.Mybatis 缓存 什么是缓存:存在于内存中的临时数据。 为什么使用缓存:减少和数据库的交互次数,提高执行效率。 同时问题来了,什么样数据适用于缓存,什么样数据不适应缓存。
适用于缓存: 经常查询并且不经常改变的。 数据的正确与否对最终结果影响不大的。(存在缓存和数据库中数据不同步问题)
不适用于缓存: 经常改变的数据 数据的正确与否对最终结果影响很大的。例如:商品的库存,银行的汇率,股市的牌价。
Mybatis 一级缓存 一级缓存: 它指的是Mybatis中SqlSession对象的缓存。 当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供一块区域中。 该区域的结构是一个Map。当我们再次查询同样的数据,mybatis会先去sqlsession中 查询是否有,有的话直接拿出来用。 当SqlSession对象消失时,mybatis的一级缓存也就消失了。
编写用户持久层 Dao 接口IUserDao
:
1 2 3 4 5 6 7 8 public interface IUserDao { User findById (Integer userId) ; }
编写用户持久层映射文件IUserDao.xml
1 2 3 4 5 6 7 8 9 <?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.mybatis.dao.IUserDao" > <select id ="findById" parameterType ="INT" resultType ="user" useCache ="true" > select * from user where id = #{uid} </select >
编写测试类:
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 private InputStream in; private SqlSessionFactory factory; private SqlSession sqlSession; private IUserDao userDao; @Before public void init () throws Exception { in = Resources.getResourceAsStream("SqlMapConfig.xml" ); factory = new SqlSessionFactoryBuilder().build(in); sqlSession = factory.openSession(true ); userDao = sqlSession.getMapper(IUserDao.class); } @After public void destroy () throws Exception { sqlSession.close(); in.close(); } @Test public void testFirstLevelCache () { User user1 = userDao.findById(41 ); System.out.println(user1); User user2 = userDao.findById(41 ); System.out.println(user2); System.out.println(user1 == user2); }
通过执行测试类,发现输出结果为True
,同时输出的对象名也是相同的。并且sql只执行了一次。换句话说,第一次是查询,第二次直接从缓存中取。
1 2 3 4 5 6 - Opening JDBC Connection - Created connection 243745864. - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@e874448 ] - ==> Preparing: select * from user where id = ? - ==> Parameters: 41 (Integer) - <== Total: 1
如果这个时候session
关闭了,缓存也会消失。
通过sqlSession.close()方法关闭一级缓存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Test public void testFirstLevelCache () { User user1 = userDao.findById(41 ); System.out.println(user1); sqlSession.close(); 再次获取SqlSession对象 sqlSession = factory.openSession(); userDao = sqlSession.getMapper(IUserDao.class); User user2 = userDao.findById(41 ); System.out.println(user2); System.out.println(user1 == user2); }
通过使用sqlSession.clearCache()方法关闭一级缓存 修改测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Test public void testFirstLevelCache () { User user1 = userDao.findById(41 ); System.out.println(user1); sqlSession.clearCache(); userDao = sqlSession.getMapper(IUserDao.class); User user2 = userDao.findById(41 ); System.out.println(user2); System.out.println(user1 == user2); }
通过执行测试类,发现输出结果为False
,同时输出的对象名也是不相同的。并且sql执行了两次。这就表示没有再用缓存,重新进行了查询。
那么问题来了,以上是手动关闭的,如果数据库的数据跟一级缓存不一致了,它是如何做到自动同步的呢?
触发清空一级缓存的情况 修改用户持久层 Dao 接口IUserDao
,增加更新方法:
1 2 3 4 5 void updateUser (User user) ;
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.mybatis.dao.IUserDao" > <update id ="updateUser" parameterType ="user" > update user set username=#{username},address=#{address} where id=#{id} </update > </mapper >
编写测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Test public void testClearlCache () { User user1 = userDao.findById(41 ); System.out.println(user1); User user2 = userDao.findById(41 ); System.out.println(user2); System.out.println(user1 == user2); }
我们先把更新用户信息这块代码注释掉,执行发现打印的是True
,sql已经也执行一次,说明存在一级缓存。
修改测试类,增加更新对象操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Test public void testClearlCache () { User user1 = userDao.findById(41 ); System.out.println(user1); user1.setUsername("update user clear cache" ); user1.setAddress("北京市海淀区" ); userDao.updateUser(user1); User user2 = userDao.findById(41 ); System.out.println(user2); System.out.println(user1 == user2); }
发现输出结果为false
,查询语句也执行了两次,第二次查询没有从缓存里面取,而是发起了一次新查询,这是为什么呢?
一级缓存的分析 我们可以发现,虽然在上面的代码中我们查询了两次,但最后只执行了一次数据库操作,这就是 Mybatis 提供给我们的一级缓存在起作用了。因为一级缓存的存在,导致第二次查询 id 为 41 的记录时,并没有发出 sql 语句从数据库中查询数据,而是从一级缓存中查询。
一级缓存是 SqlSession
范围的缓存,当调用 SqlSession
的修改,添加,删除,commit()
,close()
,clearCache()
等方法时,就会清空一级缓存 。
第一次发起查询用户 id 为 41 的用户信息,先去找缓存中是否有 id 为 41 的用户信息,如果没有,从数据库查询用户信息。 得到用户信息,将用户信息存储到一级缓存中。 如果 sqlSession
去执行 commit
操作(执行插入、更新、删除 ),清空 SqlSession
中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息 ,避免脏读。 第二次发起查询用户 id 为 41 的用户信息,先去找缓存中是否有 id 为 41 的用户信息,缓存中有,直接从缓存中获取用户信息。
所以,我们刚刚调用了Update()
方法,导致一级缓存被清空,所以此时会再次发起查询。也就是不再会从缓存中获取。
Mybatis 二级缓存 二级缓存:它指的是Mybatis中SqlSessionFactory
对象的缓存。由同一个SqlSessionFactory
对象创建的SqlSession
共享其缓存。
创建测试类:
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 private InputStream in; private SqlSessionFactory factory; @Before public void init () throws Exception { in = Resources.getResourceAsStream("SqlMapConfig.xml" ); 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.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); }
执行测试类,发现输出结果为False
,并且执行了两次,明明是由同一个SqlSessionFactory
对象创建的SqlSession
共享其缓存,应该输出True
才对,此时并没有二级缓存概念。这是为什么?
二级缓存的使用步骤: 第一步:让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置) 第二步:让当前的映射文件支持二级缓存(在IUserDao.xml中配置) 第三步:让当前的操作支持二级缓存(在select标签中配置)
修改SqlMapConfig.xml
,修改cacheEnabled
为True,默认为True
1 2 3 4 5 6 7 8 9 10 <?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 > <settings > <setting name ="cacheEnabled" value ="true" /> </settings > </configuration >
修改IUserDao.xml
映射文件,开启user支持二级缓存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?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.mybatis.dao.IUserDao" > <cache /> <select id ="findById" parameterType ="INT" resultType ="user" useCache ="true" > select * from user where id = #{uid} </select > </mapper >
再次执行测试类,发现只执行了一次,说明二级缓存生效了,但是输出结果为False
,说明两个对象不一致。是因为二级缓存存放的是数据 而不是对象 。
查看上图,发现二级环境存放是类似json格式的数据,谁需要就会把数据拿过来,创建一个新的对象,并把数据封装进去。虽然没有发起新的查询,但是却创建了一个新的对象。
14.Mybatis 注解开发 这几年来注解开发越来越流行,Mybatis 也可以使用注解开发方式,这样我们就可以减少编写 Mapper 映射文件了。本次我们先围绕一些基本的 CRUD 来学习,再学习复杂映射关系及延迟加载。 本章节注解开发只涉及Mapper
映射问题,并不涉及SqlMapConfig.xml
。
环境搭建 创建一个Maven
工程,修改pom.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 <?xml version="1.0" encoding="UTF-8"?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <groupId > com.mybatis</groupId > <artifactId > annotation_mybatis</artifactId > <version > 1.0-SNAPSHOT</version > <packaging > jar</packaging > <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 > </dependency > <dependency > <groupId > log4j</groupId > <artifactId > log4j</artifactId > <version > 1.2.12</version > </dependency > <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <version > 4.10</version > </dependency > </dependencies > </project >
创建包com.mybatis.domain,在domain
创建User
实体对象:
1 2 3 4 5 6 7 8 9 public class User implements Serializable { private Integer id; private String username; private String address; private String sex; private Date birthday; }
创建包com.mybatis.dao,在dao
下创建数据库Dao层接口文件IUserDao
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public interface IUserDao { @Select("select * from user") List<User> findAll () ; }
在resources
目录下创建包com.mybatis.dao
。
在resources
目录下创建jdbcConfig.properties
:
1 2 3 4 jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql: jdbc.username=root jdbc.password=1234
在resources
目录下创建log4j.properties
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # Set root category priority to INFO and its only appender to CONSOLE. #log4j.rootCategory=INFO, CONSOLE debug info warn error fatal log4j.rootCategory=debug, CONSOLE, LOGFILE # Set the enterprise logger category to FATAL and its only appender to CONSOLE. log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE # CONSOLE is set to be a ConsoleAppender using a PatternLayout. log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15. 15t] %-5p %30. 30c %x - %m\n # LOGFILE is set to be a File appender using a PatternLayout. log4j.appender.LOGFILE=org.apache.log4j.FileAppender log4j.appender.LOGFILE.File=d:\axis.log log4j.appender.LOGFILE.Append=true log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15. 15t] %-5p %30. 30c %x - %m\n
在resources
目录下创建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 <?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.mybatis.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.mybatis.dao.IUserDao" > </mapper > </mappers > </configuration >
环境到此搭建完成。
创建测试类:
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 public static void main (String[] args) throws Exception { InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml" ); SqlSessionFactory factory = new SqlSessionFactoryBuilder().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(); } }
注:我们在使用注解开发的时候,就不能再同一Dao下(目录)出现xml映射文件。
Mybatis 的常用注解说明 @Insert: 实现新增@Update: 实现更新@Delete: 实现删除@Select: 实现查询@Result: 实现结果集封装@Results: 可以与@Result 一起使用,封装多个结果集@ResultMap: 实现引用@Results 定义的封装@One: 实现一对一结果集封装@Many: 实现一对多结果集封装@SelectProvider: 实现动态 SQL 映射@CacheNamespace: 实现注解二级缓存的使用
使用注解实现基本 CRUD @Insert 修改IUserDao
,增加保存方法
1 2 3 4 5 6 @Insert("insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday})") void saveUser (User 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 public class AnnotationCRUDTest { private InputStream in; private SqlSessionFactory factory; private SqlSession session; private IUserDao userDao; @Before public void init () throws Exception { in = Resources.getResourceAsStream("SqlMapConfig.xml" ); factory = new SqlSessionFactoryBuilder().build(in); session = factory.openSession(); userDao = session.getMapper(IUserDao.class); } @After public void destroy () throws Exception { session.commit(); session.close(); in.close(); } @Test public void testSave () { User user = new User(); user.setUsername("mybatis annotation" ); user.setAddress("北京市昌平区" ); userDao.saveUser(user); } }
@Update 修改IUserDao
,增加更新方法
1 2 3 4 5 6 @Update("update user set username=#{username},sex=#{sex},birthday=#{birthday},address=#{address} where id=#{id}") void updateUser (User 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 public class AnnotationCRUDTest { private InputStream in; private SqlSessionFactory factory; private SqlSession session; private IUserDao userDao; @Before public void init () throws Exception { in = Resources.getResourceAsStream("SqlMapConfig.xml" ); factory = new SqlSessionFactoryBuilder().build(in); session = factory.openSession(); userDao = session.getMapper(IUserDao.class); } @After public void destroy () throws Exception { session.commit(); session.close(); in.close(); } @Test public void testUpdate () { User user = new User(); user.setId(57 ); user.setUsername("mybatis annotation update" ); user.setAddress("北京市海淀区" ); user.setSex("男" ); user.setBirthday(new Date()); userDao.updateUser(user); } }
@Delete 修改IUserDao
,增加删除方法
1 2 3 4 5 6 @Delete("delete from user where id=#{id} ") void deleteUser (Integer userId) ;
测试类:
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 public class AnnotationCRUDTest { private InputStream in; private SqlSessionFactory factory; private SqlSession session; private IUserDao userDao; @Before public void init () throws Exception { in = Resources.getResourceAsStream("SqlMapConfig.xml" ); factory = new SqlSessionFactoryBuilder().build(in); session = factory.openSession(); userDao = session.getMapper(IUserDao.class); } @After public void destroy () throws Exception { session.commit(); session.close(); in.close(); } @Test public void testDelete () { userDao.deleteUser(51 ); } }
@Select 修改IUserDao
,增加查询方法
1 2 3 4 5 6 7 8 @Select("select * from user where username like '%${value}%' ") List<User> findUserByName (String username) ;
测试类:
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 public class AnnotationCRUDTest { private InputStream in; private SqlSessionFactory factory; private SqlSession session; private IUserDao userDao; @Before public void init () throws Exception { in = Resources.getResourceAsStream("SqlMapConfig.xml" ); factory = new SqlSessionFactoryBuilder().build(in); session = factory.openSession(); userDao = session.getMapper(IUserDao.class); } @After public void destroy () throws Exception { session.commit(); session.close(); in.close(); } @Test public void testFindByName () { List<User> users = userDao.findUserByName("mybatis" ); for (User user : users){ System.out.println(user); } } }
修改IUserDao
,增加查询方法
1 2 3 4 5 6 @Select("select count(*) from user ") int findTotalUser () ;
测试类:
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 public class AnnotationCRUDTest { private InputStream in; private SqlSessionFactory factory; private SqlSession session; private IUserDao userDao; @Before public void init () throws Exception { in = Resources.getResourceAsStream("SqlMapConfig.xml" ); factory = new SqlSessionFactoryBuilder().build(in); session = factory.openSession(); userDao = session.getMapper(IUserDao.class); } @After public void destroy () throws Exception { session.commit(); session.close(); in.close(); } @Test public void testFindTotal () { int total = userDao.findTotalUser(); System.out.println(total); } }
使用注解实现复杂关系映射开发 Mybatis是支持对象属性名可以和数据库不一致的,那么xml我们设置过,那么注解该怎么实现两者的对应关系呢?
实现复杂关系映射之前我们可以在映射文件中通过配置<resultMap>来实现,在使用注解开发时我们需要借助@Results
注解,@Result
注解,@One
注解,@Many
注解。
实体类属性和数据库表中列的对应关系 一对一查询注解配置 一对多查询注解配置 1.注解建立实体类属性和数据库表中列的对应关系
为了模拟实体类与列名不一致,我们修改实体User
1 2 3 4 5 6 7 8 public class User implements Serializable { private Integer userId; private String userName; private String userAddress; private String userSex; private Date userBirthday;
这时候查询的数据是无法正常封装的,只有UserName会封装成功,因为windows下Mysql不分大小写。有人会说,使用别名可以解决这个问题,但是如果查询的方法很多,我们就需要每个都改一遍,显然不太合适。MyBatis就为我们提供了一个注解。
@Results 注解 :代替的是标签<resultMap>该注解中可以使用单个@Result
注解,也可以使用@Result
集合。@Results({@Result(),@Result()})或@Results(@Result())
@Resutl 注解 :代替了 <id>**标签和 **<result> 标签 @Result 中属性介绍: id 是否是主键字段,True或False。默认为False column 数据库的列名 property 需要装配的属性名 one 需要使用的@One 注解(@Result(one=@One)()))
many 需要使用的@Many 注解(@Result(many=@many)()))
修改数据库Dao接口IUserDao
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public interface IUserDao { @Select("select * from user") @Results(value={ @Result(id=true,column = "id",property = "userId"), @Result(column = "username",property = "userName"), @Result(column = "address",property = "userAddress"), @Result(column = "sex",property = "userSex"), @Result(column = "birthday",property = "userBirthday"), }) List<User> findAll () ; @Select("select * from user where id=#{id} ") User findById (Integer userId) ;
这时候我们在执行findAll()
方法,发现可以正常封装了。我们在执行findById()
方法,发现怎么还是和第一次一样,无法正常封装数据。难道我们每条Sql都要配置对应关系吗?
当然不是,Mybatis在@Results
中提供了一个id
属性。这个id属性就是给定义(数据库与实体关系)提供一个标识,方便其它Sql引用。在需要引用的sql上加@ResultMap
注解,注解value值为定义的id值。可以有多个value。也就是可以定义一个字段与数据库对应关系,也可以定义多个字段与数据库对应关系。
修改数据库Dao接口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 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 = "address",property = "userAddress"), @Result(column = "sex",property = "userSex"), @Result(column = "birthday",property = "userBirthday"), }) List<User> findAll () ; @Select("select * from user where id=#{id} ") @ResultMap("userMap") User findById (Integer userId) ; @Select("select * from user where username like #{username} ") @ResultMap("userMap") List<User> findUserByName (String username) ;
同理,模糊查询也可以直接使用@ResultMap("userMap")
注解。
测试类:
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 public class AnnotationCRUDTest { private InputStream in; private SqlSessionFactory factory; private SqlSession session; private IUserDao userDao; @Before public void init () throws Exception { in = Resources.getResourceAsStream("SqlMapConfig.xml" ); factory = new SqlSessionFactoryBuilder().build(in); session = factory.openSession(); userDao = session.getMapper(IUserDao.class); } @After public void destroy () throws Exception { session.commit(); session.close(); in.close(); } @Test public void testFindAll () { List<User> users = userDao.findAll(); for (User user : users){ System.out.println("---每个用户的信息----" ); System.out.println(user); System.out.println(user.getAccounts()); } } @Test public void testFindOne () { User user = userDao.findById(57 ); System.out.println(user); } @Test public void testFindByName () { List<User> users = userDao.findUserByName("%mybatis%" ); for (User user : users){ System.out.println(user); } } }
2.一对一查询注解配置
@One 注解(一对一) 代替了<assocation>标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。 @One 注解属性介绍: select 指定用来多表查询的 sqlMapper fetchType
会覆盖全局的配置参数 lazyLoadingEnabled。。
使用格式: @Result(column=" ",property="",one=@One(select=""))
需求:加载账户信息时并且加载该账户的用户信息,根据情况可实现延迟加载。(注解方式实现)我们还是用账户和用户为例,用户实体我们已经创建完成,只需要创建对应账户实体类。
1 2 3 4 5 6 7 8 9 10 public class Account implements Serializable { private Integer id; private Integer uid; private Double money; private User user; }
创建数据库Dao接口,IAccountDao
,并添加方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public interface IAccountDao { @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(property = "user",column = "uid",one=@One(select="com.mybatis.dao.IUserDao.findById",fetchType= FetchType.EAGER)) //fetchType:LAZY(延迟加载),EAGER(立即加载),DEFAULT(默认)。一对一选择立即加载。一对多延迟加载。 }) List<Account> findAll () ; }
测试类:
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 public class AccountTest { private InputStream in; private SqlSessionFactory factory; private SqlSession session; private IAccountDao accountDao; @Before public void init () throws Exception { in = Resources.getResourceAsStream("SqlMapConfig.xml" ); factory = new SqlSessionFactoryBuilder().build(in); session = factory.openSession(); accountDao = session.getMapper(IAccountDao.class); } @After public void destroy () throws Exception { session.commit(); session.close(); in.close(); } @Test public void testFindAll () { List<Account> accounts = accountDao.findAll(); for (Account account : accounts){ System.out.println("----每个账户的信息-----" ); System.out.println(account); System.out.println(account.getUser()); } } }
3.一对多查询注解配置
@Many 注解(多对一) 代替了<Collection>标签,是是多表查询的关键,在注解中用来指定子查询返回对象集合。 注意:聚集元素用来处理“一对多”的关系。需要指定映射的 Java 实体类的属性,属性的 javaType
(一般为 ArrayList
)但是注解中可以不定义; 使用格式:@Result(property="",column="",many=@Many(select=""))
需求:查询用户信息时,也要查询他的账户列表。使用注解方式实现。 分析:一个用户具有多个账户信息,所以形成了用户(User)与账户(Account)之间的一对多关系。
修改User
实体对象:
1 2 3 4 5 6 7 8 9 10 11 public class User implements Serializable { private Integer userId; private String userName; private String userAddress; private String userSex; private Date userBirthday; private List<Account> accounts;
在IAccountDao
,新增一个方法。供一对多查询
1 2 3 4 5 6 7 @Select("select * from account where uid = #{userId}") List<Account> findAccountByUid (Integer userId) ;
修改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 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 = "address",property = "userAddress"), @Result(column = "sex",property = "userSex"), @Result(column = "birthday",property = "userBirthday"), @Result(property = "accounts",column = "id", many = @Many(select = "com.mybatis.dao.IAccountDao.findAccountByUid", fetchType = FetchType.LAZY)) //延迟加载 }) List<User> findAll () ; @Select("select * from user where id=#{id} ") @ResultMap("userMap") User findById (Integer userId) ; }
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 public class AnnotationCRUDTest { private InputStream in; private SqlSessionFactory factory; private SqlSession session; private IUserDao userDao; @Before public void init () throws Exception { in = Resources.getResourceAsStream("SqlMapConfig.xml" ); factory = new SqlSessionFactoryBuilder().build(in); session = factory.openSession(); userDao = session.getMapper(IUserDao.class); } @After public void destroy () throws Exception { session.commit(); session.close(); in.close(); } @Test public void testFindAll () { List<User> users = userDao.findAll(); for (User user : users){ System.out.println("---每个用户的信息----" ); System.out.println(user); System.out.println(user.getAccounts()); } } }
Mybatis 基于注解的二级缓存 关于一级缓存,我们用IUserDao
下的findById()
方法演示
1 2 3 4 5 6 7 8 9 @Test public void testFindOne () { User user = userDao.findById(57 ); System.out.println(user); User user2 = userDao.findById(57 ); System.out.println(user2); System.out.println(user==user2); }
执行两次,发现两个对象是一致的,且只执行了一次,所以一级缓存我们无需过多关心。
在 SqlMapConfig
中开启二级缓存支持
1 2 3 4 5 6 7 8 9 10 <?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 > <settings > <setting name ="cacheEnabled" value ="true" /> </settings > </configuration >
在持久层接口中使用注解配置二级缓存:**@CacheNamespace(blocking = true)**
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 @CacheNamespace(blocking = true) 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 = "address",property = "userAddress"), @Result(column = "sex",property = "userSex"), @Result(column = "birthday",property = "userBirthday"), @Result(property = "accounts",column = "id", many = @Many(select = "com.itheima.dao.IAccountDao.findAccountByUid", fetchType = FetchType.LAZY)) }) List<User> findAll () ; @Select("select * from user where id=#{id} ") @ResultMap("userMap") User findById (Integer userId) ; @Select("select * from user where username like #{username} ") @ResultMap("userMap") List<User> findUserByName (String username) ; }
运行测试类:
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 public class SecondLevelCatchTest { private InputStream in; private SqlSessionFactory factory; @Before public void init () throws Exception { in = Resources.getResourceAsStream("SqlMapConfig.xml" ); factory = new SqlSessionFactoryBuilder().build(in); } @After public void destroy () throws Exception { in.close(); } @Test public void testFindOne () { SqlSession session = factory.openSession(); IUserDao userDao = session.getMapper(IUserDao.class); User user = userDao.findById(57 ); System.out.println(user); session.close(); SqlSession session1 = factory.openSession(); IUserDao userDao1 = session1.getMapper(IUserDao.class); User user1 = userDao1.findById(57 ); System.out.println(user1); session1.close(); } }
执行测试类,发现虽然打印了两次,但是只执行了一次,说明开始使用了二级缓存。