Mybatis
1.由JDBC程序开始
1.1. 创建工程
1.2. JDBC程序
1 public static void main(String[] args) throws Exception { 2 // 加载驱动 3 Class.forName("com.mysql.jdbc.Driver"); 4 5 String url = "jdbc:mysql://127.0.0.1:3306/mybatis"; 6 String user = "root"; 7 String password = "123456"; 8 9 Connection connection = null; 10 PreparedStatement statement = null; 11 ResultSet resultSet = null; 12 13 try { 14 // 创建数据库连接 15 connection = DriverManager.getConnection(url, user, password); 16 17 // 创建Statement对象 18 String sql = "SELECT * FROM tb_user WHERE user_name = ?"; 19 statement = connection.prepareStatement(sql); 20 21 // 设置参数,下标从1开始 22 statement.setString(1, "zhangsan"); 23 24 // 执行sql,变量结果集 25 resultSet = statement.executeQuery(); 26 while (resultSet.next()) { 27 System.out.println("id: " + resultSet.getString("id")); 28 System.out.println("userName: " + resultSet.getString("user_name")); 29 System.out.println("name: " + resultSet.getString("name")); 30 System.out.println("age: " + resultSet.getString("age")); 31 } 32 } finally { 33 // 释放资源 34 if (null != resultSet) { 35 resultSet.close(); 36 } 37 if (null != statement) { 38 statement.close(); 39 } 40 if (null != connection) { 41 connection.close(); 42 } 43 } 44 }
1.3. 从jdbc程序中找出问题
1、将数据库连接、用户名、密码等信息硬编码到java代码中,造成更换环境需要修改java代码,重新编译,解决:放置到外部配置文件中;
2、频繁的创建连接、释放连接资源,造成了资源浪费,效率低,解决:使用连接池解决;
3、sql语句硬编码到java代码中,实际开发中,需求经常变更,维护sql的成本变高,解决:外部配置;
4、设置参数:
a) 写死到java代码中;
b) 需要人为的判断参数类型以及设置参数下标;(下标误以为是从0开始,实际应该是从1开始)
5、变量结果集不方便,需要手动封装对象,取值时需要判断数据类型,解决:能否自动将结果集映射为java对象?
2.Mybatis简介
Mybatis的前身是iBatis,Apache的一个开源项目,2010年这个项目从Apache迁移到Google Code改名为Mybatis 之后将版本升级到3.X,其官网:http://blog.mybatis.org/,从3.2版本之后迁移到github,目前最新稳定版本为:3.2.8。
Mybatis是一个类似于Hibernate的ORM持久化框架,支持普通SQL查询,存储过程以及高级映射。Mybatis通过使用简单的XML或注解用于配置和原始映射,将接口和POJO对象映射成数据库中的记录。
由于Mybatis是直接基于JDBC做了简单的映射包装,所有从性能角度来看:JDBC > Mybatis > Hibernate
2.1.Mybatis的整体架构
1、配置2类配置文件,其中一类是:Mybatis-Config.xml(名字不是写死,随便定义),另一类:Mapper.xml(多个),定义了sql片段;
2、通过配置文件得到SqlSessionFactory
3、通过SqlSessionFactory得到SqlSession(操作数据库)
4、通过底层的Executor(执行器)执行sql,Mybatis提供了2种实现,一种是基本实现,另一种带有缓存功能的实现;
5、通过MappedStatement定义了sql,做sql的执行(数据库);
6、参数输入:POJO、基本数据类型、Map等等;
7、结果输出:POJO、基本数据类型、Map、List等等;
2.2.Mybatis的下载
Mybatis的官网:http://blog.mybatis.org/
下载地址(3.2.8):https://github.com/mybatis/mybatis-3/releases
官方文档:http://mybatis.github.io/mybatis-3/
官方文档(中文版):http://mybatis.github.io/mybatis-3/zh/index.html
2.3.第一个Mybatis程序
2.3.1.添加依赖
<!-- Mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> </dependency>
2.3.2.构造SqlSessionFactory
1 public static void main(String[] args) throws Exception { 2 //定义配置文件的路径 3 String resource = "mybatis-config.xml"; 4 //读取配置文件 5 InputStream inputStream = Resources.getResourceAsStream(resource); 6 //通过SqlSessionFactoryBuilder构建SqlSessionFactory对象 7 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 8 //获得sqlsession,设置自动提交 9 SqlSession session = sqlSessionFactory.openSession(true); 10 11 System.out.println(session); 12 13 //释放session资源 14 session.close(); 15 16 }
2.3.3.创建主配置文件:mybatis-config.xml
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE configuration 3 PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 4 "http://mybatis.org/dtd/mybatis-3-config.dtd"> 5 <configuration> 6 7 <!-- 引入外部配置文件 --> 8 <properties resource="jdbc.properties"></properties> 9 10 <!-- 环境 --> 11 <environments default="development"> 12 <environment id="development"> 13 <transactionManager type="JDBC" /> 14 <dataSource type="POOLED"> 15 <property name="driver" value="${jdbc.driver}" /> 16 <property name="url" value="${jdbc.url}" /> 17 <property name="username" value="${jdbc.username}" /> 18 <property name="password" value="${jdbc.password}" /> 19 </dataSource> 20 </environment> 21 </environments> 22 <!-- 配置mapper配置文件 --> 23 <!-- <mappers> <mapper resource="org/mybatis/example/BlogMapper.xml"/> </mappers> --> 24 </configuration>
jdbc.properties:
1 1 jdbc.driver=com.mysql.jdbc.Driver 2 2 jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true 3 3 jdbc.username=root 4 4 jdbc.password=123456
2.4.查询实现
1 public static void main(String[] args) throws Exception { 2 //定义配置文件的路径 3 String resource = "mybatis-config.xml"; 4 //读取配置文件 5 InputStream inputStream = Resources.getResourceAsStream(resource); 6 //通过SqlSessionFactoryBuilder构建SqlSessionFactory对象 7 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 8 //获得sqlsession,设置自动提交 9 SqlSession session = sqlSessionFactory.openSession(true); 10 11 //第一个参数:Statement,第二个参数:输入参数 12 User user = session.selectOne("cn.itcast.mybatis.queryUserByUserName","zhangsan"); 13 System.out.println(user); 14 15 //释放session资源 16 session.close(); 17 18 }
注意:
1、使用Statement,namespace.id(指mapper文件中sql片段id,即dao接口中的方法名);
2、返回类型要和resultType指定的类型一致;
2.5.Mybatis使用步骤总结:
1) 创建SqlSessionFactory
2) 通过SqlSessionFactory创建SqlSession对象
3) 通过SqlSession操作数据库
4) 调用session.commit()提交事务
5) 调用session.close()关闭会话
2.6. 实现DAO的CRUD
2.6.1. 定义接口
1 public interface UserDAO { 2 /** 3 * 根据id查询用户信息 4 * @param id 5 * @return 6 */ 7 public User queryUserById(Long id); 8 9 /** 10 * 查询全部数据 11 * 12 * @return 13 */ 14 public List<User> queryAll(); 15 16 /** 17 * 新增用户 18 * @param user 19 */ 20 public void saveUser(User user); 21 22 /** 23 * 更新用户信息 24 * @param user 25 */ 26 public void updateUser(User user); 27 28 /** 29 * 根据ID删除用户数据 30 * @param id 31 */ 32 public void deleteUserById(Long id); 33 }
2.6.2.编写实现类
1 public class UserDAOImpl implements UserDAO { 2 3 private SqlSession session = null; 4 5 public UserDAOImpl(SqlSession session) { 6 this.session = session; 7 } 8 9 public User queryUserById(Long id) { 10 return this.session.selectOne("cn.itcast.mybatis.dao.UserDAO.queryUserById", id); 11 } 12 13 public List<User> queryAll() { 14 return this.session.selectList("cn.itcast.mybatis.dao.UserDAO.queryAll"); 15 } 16 17 public void saveUser(User user) { 18 this.session.insert("cn.itcast.mybatis.dao.UserDAO.saveUser", user); 19 // this.session.commit();// 如果不是自动提交 20 } 21 22 public void updateUser(User user) { 23 this.session.update("cn.itcast.mybatis.dao.UserDAO.updateUser", user); 24 } 25 26 public void deleteUserById(Long id) { 27 this.session.delete("cn.itcast.mybatis.dao.UserDAO.deleteUserById", id); 28 } 29 30 }
2.6.3.编写mapper配置文件
1 <mapper namespace="cn.itcast.mybatis.dao.UserDAO" > 2 <select id="queryUserById" resultType="cn.itcast.mybatis.pojo.User"> 3 SELECT *,user_name userName FROM tb_user WHERE id = #{id} 4 </select> 5 6 <select id="queryAll" resultType="cn.itcast.mybatis.pojo.User"> 7 SELECT *,user_name userName FROM tb_user 8 </select> 9 10 <insert id="saveUser" parameterType="cn.itcast.mybatis.pojo.User"> 11 INSERT INTO tb_user ( 12 id, 13 user_name, 14 password, 15 name, 16 age, 17 sex, 18 birthday, 19 created, 20 updated 21 ) 22 VALUES 23 ( 24 NULL, 25 #{userName}, 26 #{password}, 27 #{name}, 28 #{age}, 29 #{sex}, 30 #{birthday}, 31 NOW(), 32 NOW() 33 ); 34 </insert> 35 36 <update id="updateUser" parameterType="cn.itcast.mybatis.pojo.User"> 37 UPDATE tb_user 38 SET 39 user_name = #{userName}, 40 password = #{password}, 41 name = #{name}, 42 age = #{age}, 43 sex = #{sex}, 44 birthday = #{birthday}, 45 updated = NOW() 46 WHERE 47 (id = #{id}); 48 </update> 49 50 <delete id="deleteUserById" parameterType="java.lang.Long"> 51 DELETE FROM tb_user WHERE id = #{id} 52 </delete> 53 54 </mapper>
2.6.4. 加入到Mybatis的配置文件中(容易忽略的一步)
1 <mappers> 2 <mapper resource="UserMapper.xml" /> 3 <mapper resource="UserDAOMapper.xml" /> 4 </mappers>
2.6.5.测试
1 private UserDAO userDAO = null; 2 3 @Before 4 public void setUp() throws Exception { 5 // 定义配置文件的路径 6 String resource = "mybatis-config.xml"; 7 // 读取配置文件 8 InputStream inputStream = Resources.getResourceAsStream(resource); 9 // 通过SqlSessionFactoryBuilder构建SqlSessionFactory对象 10 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 11 // 获得sqlsession,设置自动提交 12 SqlSession session = sqlSessionFactory.openSession(true); 13 14 userDAO = new UserDAOImpl(session); 15 } 16 17 @Test 18 public void testQueryUserById() { 19 User user = this.userDAO.queryUserById(1L); 20 System.out.println(user); 21 } 22 23 @Test 24 public void testQueryAll() { 25 List<User> users = this.userDAO.queryAll(); 26 for (User user : users) { 27 System.out.println(user); 28 } 29 } 30 31 @Test 32 public void testSaveUser() { 33 User user = new User(); 34 user.setAge(20); 35 user.setBirthday(new Date()); 36 user.setName("0102_2"); 37 user.setPassword("123"); 38 user.setSex(2); 39 user.setuserName("user_name_0102_2"); 40 this.userDAO.saveUser(user); 41 } 42 43 @Test 44 public void testUpdateUser() { 45 User user = new User(); 46 user.setId(54L); 47 user.setAge(21); 48 user.setBirthday(new Date()); 49 user.setName("0102_02"); 50 user.setPassword("123456"); 51 user.setSex(1); 52 user.setuserName("user_name_0102_02"); 53 this.userDAO.updateUser(user); 54 } 55 56 @Test 57 public void testDeleteUserById() { 58 this.userDAO.deleteUserById(54L); 59 }
2.7. 编写DAO的实现类发现的问题
1、session需要构造传入;
2、调用Statement不方便;
3、通用性不好,实现方法非常的类似;
2.8. 使用动态代理实现接口的实现类
如何得到动态代理:
userDAO = session.getMapper(UserDAO.class);
注意:
1、保证命名空间和接口的全路径一致;
<mapper namespace="cn.itcast.mybatis.dao.UserDAO" >
2、Statement的id和接口中的方法名一致
3、加入到mybatis-config.xml中
2.9. 使用动态代理总结
使用mapper接口不用写接口实现类即可完成数据库操作,使用非常简单,也是官方所推荐的使用方法。
使用mapper接口的必须具备以几个条件:
1) Mapper的namespace必须和mapper接口的全路径一致。
2) Mapper接口的方法名必须和sql定义的id一致。
3) Mapper接口中方法的输入参数类型必须和sql定义的parameterType一致。
4) Mapper接口中方法的输出参数类型必须和sql定义的resultType一致。
3.Mybatis-Config配置
- properties 属性
- settings 设置
- typeAliases 类型别名
- typeHandlers 类型处理器
- objectFactory 对象工厂
- plugins 插件
- environments 环境
-
- environment 环境变量
- transactionManager 事务管理器
- dataSource 数据源
- environment 环境变量
- mappers 映射器
Mybatis的配置文件中配置项是有顺序的,即按照上面的顺序;
3.1.properties
1 <!-- 引入外部配置文件 --> 2 <properties resource="jdbc.properties"></properties>
3.2.setting
这些是极其重要的调整, 它们会修改 MyBatis 在运行时的行为方式。 下面这个表格描述 了设置信息,它们的含义和默认值。
设置参数 |
描述 |
有效值 |
默认值 |
cacheEnabled |
这个配置使全局的映射器启用或禁用 缓存。 |
true | false |
true |
lazyLoadingEnabled |
全局启用或禁用延迟加载。当禁用时, 所有关联对象都会即时加载。 This value can be superseded for an specific relation by using the fetchTypeattribute on it. |
true | false |
false |
aggressiveLazyLoading |
当启用时, 有延迟加载属性的对象在被 调用时将会完全加载任意属性。否则, 每种属性将会按需要加载。 |
true | false |
true |
multipleResultSetsEnabled |
允许或不允许多种结果集从一个单独 的语句中返回(需要适合的驱动) |
true | false |
true |
useColumnLabel |
使用列标签代替列名。 不同的驱动在这 方便表现不同。 参考驱动文档或充分测 试两种方法来决定所使用的驱动。 |
true | false |
true |
useGeneratedKeys |
允许 JDBC 支持生成的键。 需要适合的 驱动。 如果设置为 true 则这个设置强制 生成的键被使用, 尽管一些驱动拒绝兼 容但仍然有效(比如 Derby) |
true | false |
False |
autoMappingBehavior |
指定 MyBatis 如何自动映射列到字段/ 属性。PARTIAL 只会自动映射简单, 没有嵌套的结果。FULL 会自动映射任 意复杂的结果(嵌套的或其他情况) 。 |
NONE, PARTIAL, FULL |
PARTIAL |
defaultExecutorType |
配置默认的执行器。SIMPLE 执行器没 有什么特别之处。REUSE 执行器重用 预处理语句。BATCH 执行器重用语句 和批量更新 |
SIMPLE REUSE BATCH |
SIMPLE |
defaultStatementTimeout |
设置超时时间, 它决定驱动等待一个数 据库响应的时间。 |
Any positive integer |
Not Set (null) |
safeRowBoundsEnabled |
Allows using RowBounds on nested statements. |
true | false |
False |
mapUnderscoreToCamelCase |
Enables automatic mapping from classic database column names A_COLUMN to camel case classic Java property names aColumn. |
true | false |
False |
localCacheScope |
MyBatis uses local cache to prevent circular references and speed up repeated nested queries. By default (SESSION) all queries executed during a session are cached. If localCacheScope=STATEMENT local session will be used just for statement execution, no data will be shared between two different calls to the same SqlSession. |
SESSION | STATEMENT |
SESSION |
jdbcTypeForNull |
Specifies the JDBC type for null values when no specific JDBC type was provided for the parameter. Some drivers require specifying the column JDBC type but others work with generic values like NULL, VARCHAR or OTHER. |
JdbcType enumeration. Most common are: NULL, VARCHAR and OTHER |
OTHER |
lazyLoadTriggerMethods |
Specifies which Object's methods trigger a lazy load |
A method name list separated by commas |
equals,clone,hashCode,toString |
defaultScriptingLanguage |
Specifies the language used by default for dynamic SQL generation. |
A type alias or fully qualified class name. |
org.apache.ibatis.scripting.xmltags.XMLDynamicLanguageDriver |
callSettersOnNulls |
当结果集中含有Null值时是否执行映射对象的setter或者Map对象的put方法。此设置对于原始类型如int,boolean等无效。 |
true | false |
false |
logPrefix |
Specifies the prefix string that MyBatis will add to the logger names. |
Any String |
Not set |
logImpl |
Specifies which logging implementation MyBatis should use. If this setting is not present logging implementation will be autodiscovered. |
SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING |
Not set |
proxyFactory |
Specifies the proxy tool that MyBatis will use for creating lazy loading capable objects. |
CGLIB | JAVASSIST |
CGLIB |
3.3.typeAliases(别名)
类型别名是为 Java 类型命名一个短的名字。 它只和 XML 配置有关, 只用来减少类完全限定名的多余部分。
自定义别名:
1 <typeAliases> 2 <typeAlias alias="User" type="cn.itcast.mybatis.pojo.User"/> 3 <!-- 定义扫描包 --> 4 <package name="cn.itcast.mybatis.pojo"/> 5 </typeAliases>
引用:
1 <select id="queryUserByID" parameterType="long" resultType="User"> 2 SELECT * FROM tb_user WHERE id = #{id} 3 </select>
注:扫描包会生成2个别名,一个大写一个小写(User或者user);
补充注意:使用定义的别名是不区分大小写的,但一般按java规则去使用即可,即user或者User
默认别名:
别名 |
映射的类型 |
_byte |
byte |
_long |
long |
_short |
short |
_int |
int |
_integer |
int |
_double |
double |
_float |
float |
_boolean |
boolean |
string |
String |
byte |
Byte |
long |
Long |
short |
Short |
int |
Integer |
integer |
Integer |
double |
Double |
float |
Float |
boolean |
Boolean |
date |
Date |
decimal |
BigDecimal |
bigdecimal |
BigDecimal |
object |
Object |
map |
Map |
hashmap |
HashMap |
list |
List |
arraylist |
ArrayList |
collection |
Collection |
iterator |
Iterator |
3.4.typeHandlers(类型处理器)
类型处理器是在设置参数,以及从Restult中检索值来匹配java数据类型,Mybatis提供非常多的默认类型处理器,一般情况下都可以满足日常的使用,不需要自定义处理器。(这里不做深究)
3.5.plugins(插件)
MyBatis 允许你在某一点拦截已映射语句执行的调用。默认情况下,MyBatis 允许使用插件来拦截方法调用:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
这里不做过多讲解,随后参考分页插件博客!
3.6.environments(环境)
3.7.mappers
mapper映射文件的引入有3种方式:
- 路径相对于资源目录跟路径:
1 <mapper resource="mappers/userMapper.xml"/>
- 使用完整的文件路径:
1 <mapper url="file:/// D:\itcast-workspace\itcast-mybatis\src\main\resources\mappers\userMapper.xml"/>
- 使用mapper接口类路径
1 <mapper class="cn.itcast.mybatis.mapper.UserMapper"/>
注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。或可直接配个扫描包
1 <!-- Register all interfaces in a package as mappers --> 2 <mappers> 3 <package name="org.mybatis.builder"/> 4 </mappers>
4.Mapper XML 文件
Mapper映射文件是在实际开发过程中使用最多的,也是我们学习的重点。
Mapper文件中包含的元素有:
- cache – 配置给定命名空间的缓存。
- cache-ref – 从其他命名空间引用缓存配置。
- resultMap – 映射复杂的结果对象。
- sql – 可以重用的 SQL 块,也可以被其他语句引用。
- insert – 映射插入语句
- update – 映射更新语句
- delete – 映射删除语句
- select – 映射查询语句
4.1.CRUD
4.1.1.select
1 <select id="queryUserById" resultType="user"> 2 SELECT * FROM tb_user WHERE id = #{id} 3 </select>
- select标签叫Statement
- id,必要属性,在当前的命名空间下不能重复。
- 指定输出类型,resultType
- parameterType (不是必须)如果不指定,自动识别。
4.1.2. insert
1 <insert id="saveUser" parameterType="User"> 2 </insert>
- insert标签叫Statement
- id,必要属性,在当前的命名空间下不能重复。
- parameterType (不是必须)如果不指定,自动识别。
4.1.3.如何获得到自增id
1 <insert id="saveUser" parameterType="User" useGeneratedKeys="true" keyProperty="id">
- useGeneratedKeys:开启自增长映射
- keyProperty:指定id所对应对象中的属性名
- ⚠️当执行完saveUser()方法后,其返回值依然是执行sql影响的行数,并不是要获取的自增ID!Mybatis会自动将返回的主键值赋值给对象User的属性id,因此你可以通过属性的get方法获得插入的主键值: System.out.println(User.getId());
4.1.4.update
1 <update id="updateUser" parameterType="User"> 2 </update>
- update标签叫Statement
- id,必要属性,在当前的命名空间下不能重复。
- parameterType (不是必须)如果不指定,自动识别。
4.1.5.delete
1 <delete id="deleteUserById" parameterType="Long"> 2 </delete>
- delete标签叫Statement
- id,必要属性,在当前的命名空间下不能重复。
- parameterType (不是必须)如果不指定,自动识别。
4.2. 根据表名通用查询
4.2.1.创建接口
1 public interface CommonMapper { 2 3 public List<Map<String, Object>> queryByTableName(@Param("tableName")String tableName); 4 }
4.2.2.创建mapper配置文件
1 <select id="queryByTableName" resultType="HashMap"> 2 SELECT * FROM ${tableName} 3 </select>
4.2.3.面试题:#{}与${}的区别
在Mybatis的mapper中,参数传递有2种方式,一种是#{}另一种是${},两者有着很大的区别:
#{} 实现的是sql语句的预处理参数,之后执行sql中用?号代替,使用时不需要关注数据类型,Mybatis自动实现数据类型的转换。并且可以防止SQL注入。
${} 实现是sql语句的直接拼接,不做数据类型转换,需要自行判断数据类型。不能防止SQL注入。
是不是${}就没用了呢?不是的,有些情况下就必须使用${},举个例子:在分表存储的情况下,我们从哪张表查询是不确定的,也就是说sql语句不能写死,表名是动态的,查询条件的固定的,这样:SELECT * FROM ${tableName} WHERE id = #{id}
总结:
#{} 占位符,用于参数传递。
${}用于SQL拼接。
4.3.parameterType的传入参数
传入类型有三种:
1、简单类型,string、long、integer等
2、Pojo类型,User等
3、HashMap类型。
在使用#{}传参时,#{}只是做占位符,与参数名无关。
在使用${}传参时,是通过参数名获取参数的,如果没有指定参数名则可以通过value获取,如果指定则需要按照名称获取。
当DAO接口中需要传递多个参数时有三种方法传递:
- 将参数封装一个hashmap传递;
- 将参数封装成一个对象传递;
- 使用@Param注解表明参数传递;(最常用)
4.4.parameterType的传入多个参数
当DAO接口中需要传递多个参数时有两种方法传递:
- 使用默认规则获取参数;
默认规则:
a) 使用参数的下标,从0开始,#{0},#{1}
b) 使用param1、param2、param。。。。 #{param1}、#{param2}
- 使用@Param注解表明参数传递;
public User login(@Param("userName")String userName, @Param("password")String password);
注意:注解最终会转化为map传入(注解传参原理);
4.5.ResultType结果输出
输出类型有三种:
l 简单类型,string、long、integer等
l Pojo类型,User等
l HashMap类型。
4.6.ResultMap
ResultMap是Mybatis中最重要最强大的元素,使用ResultMap可以解决两大问题:
l POJO属性名和表结构字段名不一致的问题(有些情况下也不是标准的驼峰格式)
l 完成高级查询,比如说,一对一、一对多、多对多。
解决表字段名和属性名不一致的问题有两种方法:
1、(推荐使用)如果是驼峰似的命名规则可以在Mybatis配置文件中设置<setting name="mapUnderscoreToCamelCase" value="true"/>解决
2、使用ResultMap解决。
高级查询后面详细讲解。
4.7.自动映射
4.8.SQL片段
我们在java代码中会将公用的一些代码提取出来需要的地方直接调用方法即可,在Mybatis也是有类似的概念,那就是SQL片段。
在Mybatis中使用<sql id=”” />标签定义SQL片段,在需要的地方通过<include refid=“”/>引用,例如:
4.9.动态sql
MyBatis的动态SQL是基于OGNL表达式的,它可以帮助我们方便的在SQL语句中实现某些逻辑。
MyBatis中用于实现动态SQL的元素主要有:
- if
- choose(when,otherwise)
- trim
- where
- set
- foreach
4.9.1 if
需求1:查询男性用户,如果输入了姓名,进行模糊查找。
4.9.2 choose
choose元素的作用就相当于JAVA中的switch语句,基本上跟JSTL中的choose的作用和用法是一样的,通常都是与when和otherwise搭配的。
需求2:查询男性用户,如果输入了姓名则按照姓名模糊查找,如果输入了年龄则按照年龄查找。
详析:when元素表示当when中的条件满足的时候就输出其中的内容,跟JAVA中的switch效果差不多的是按照条件的顺序,当when中有条件满足的时候,就会跳出choose,即所有的when和otherwise条件中,只有一个会输出,当所有的when条件都不满足的时候就输出otherwise中的内容。
4.9.3 where
需求3:查询所有用户,如果输入了姓名,进行模糊查找;如果输入了年龄,按年龄查找(多条件查询)
详析:where元素的作用是会在写入where元素的地方输出一个where,另外一个好处是你不需要考虑where元素里面的条件输出是什么样子的,MyBatis会智能的帮你处理,如果所有的条件都不满足那么MyBatis就会查出所有的记录,如果输出后是and 开头的,MyBatis会把第一个and忽略,当然如果是or开头的,MyBatis也会把它忽略;此外,在where元素中你不需要考虑空格的问题,MyBatis会智能的帮你加上。
4.9.4 foreach
需求4:按照多个ID查询用户信息。
用途:多个id查询、删除,批量插入数据;
补充:
foreach的主要用在构建in条件中,它可以在SQL语句中进行迭代一个集合。foreach元素的属性主要有 item,index,collection,open,separator,close。item表示集合中每一个元素进行迭代时的别名,index指 定一个名字,用于表示在迭代过程中,每次迭代到的位置,open表示该语句以什么开始,separator表示在每次进行迭代之间以什么符号作为分隔 符,close表示以什么结束,在使用foreach的时候最关键的也是最容易出错的就是collection属性,该属性是必须指定的,但是在不同情况 下,该属性的值是不一样的,主要有一下3种情况:
- 如果传入的是单参数且参数类型是一个List的时候,collection属性值为list
- 如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array
- 如果传入的参数是多个的时候,我们就需要把它们封装成一个Map了,当然单参数也可以封装成map,实际上如果你在传入参数的时候,在MyBatis里面也是会把它封装成一个Map的,map的key就是参数名,所以这个时候collection属性值就是传入的List或array对象在自己封装的map里面的key
下面分别来看看上述三种情况的示例代码:
- 单参数List的类型:如上,需求4
- 单参数array数组的类型:
- 自己把参数封装成Map的类型
4.9.5 trim
trim元素的主要功能是可以在自己包含的内容前加上某些前缀,也可以在其后加上某些后缀,与之对应的属性是 prefix和suffix;可以把包含内容的首部某些内容覆盖,即忽略,也可以把尾部的某些内容覆盖,对应的属性是prefixOverrides和 suffixOverrides;正因为trim有这样的功能,所以我们也可以非常简单的利用trim来代替where元素的功能,示例代码如下:
4.9.6 set
set元素主要是用在更新操作的时候,它的主要功能和where元素其实是差不多的,主要是在包含的语句前输出一个 set,然后如果包含的语句是以逗号结束的话将会把该逗号忽略,如果set包含的内容为空的话则会出错。有了set元素我们就可以动态的更新那些修改了的 字段。下面是一段示例代码:
5.一级缓存
- Mybatis的一级缓存的作用域是session,当openSession()后,如果执行相同的SQL(相同语句和参数),Mybatis不进行执行SQL,而是从缓存中命中返回!
- 原理:Mybatis执行查询时首先去缓存区命中,如果命中直接返回,没用命中则执行SQL,从数据库中查询。
- 使用session.clearCache()强制查询不缓存。在执行insert、update、delete时会刷新缓存。
注:
1、一级缓存是默认开启的,不能关闭,手动清空缓存:session.clearCache();
2、一级缓存是session级别,不能跨session
3、当在一个session作用域中,insert、update、delete会强制刷新缓存;
6.二级缓存
1、在mybatis-config.xml中开启二级缓存的开关,<setting name="cacheEnabled" value="true"/>,虽然默认是true开启的,但建议再配一下!Value=”true”表示开启缓存(注:以下截图显示‘禁用’有误)
2、需要手动开启,在mapper.xml中设置<cache/>标签;
3、查询的结果集对象必须是可序列化的,否则在关闭session时会抛出异常;
二级缓存中还有其他的一些参数设置,具体参考文档;
6.1 使用第三方缓存实现二级缓存(自学部分)
实际上,在企业中用二级缓存较少,基本不用,我们会用其他一些缓存,如radis在业务中去控制缓存,而不是用mybatis自身的缓存机制!
7.高级查询
Mybatis作为一个ORM框架,也对SQL的高级查询做了支持,下面我们学习Mybatis下的一对一、一对多、多对多的查询。
案例说明:
此案例的业务关系是用户、订单、订单详情、商品之间的关系,其中,
一个订单只能属于一个人。
一个订单可以有多个订单详情。
一个订单详情中包含一个商品信息。
它们的关系是:
订单和人是 一对一的关系。
订单和订单详情是 一对多 的关系。
订单和商品是 多对多的关系。
7.1 一对一查询
7.1.1 第一种实现
核心思想:扩充pojo对象使结果集自动映射;
1 <select id="queryOrderAndUserByOrderNumber" parameterType="String" resultType="OrderUser"> 2 SELECT 3 o.*, 4 u.user_name, 5 u.name 6 FROM 7 tb_order o 8 LEFT JOIN tb_user u ON u.id = o.user_id 9 WHERE o.order_number = #{orderNumber} 10 </select>
7.1.2 第二种实现
核心思想:面向对象的思想;
1 <select id="queryOrderAndUser2ByOrderNumber" parameterType="String" resultMap="OrderResultMap"> 2 SELECT 3 o.*, 4 u.user_name, 5 u.name 6 FROM 7 tb_order o 8 LEFT JOIN tb_user u ON u.id = o.user_id 9 WHERE o.order_number = #{orderNumber} 10 </select> 11 <!—注意:做高级查询时,这里autoMapping自动映射默认开启是失效的,所以必须手动设置 --> 12 <resultMap type="Order" id="OrderResultMap" autoMapping="true"> 13 <!— 14 Column属性为对象表中的id列名, 15 Property属性为对象pojo中的id字段名 16 --> 17 <id column="id" property="id"/> 18 <result column="user_id" property="userId"/> 19 20 <!-- 21 association用来映射对象User, 22 属性property为Order对象中的user对象, 23 属性javaType为user的对象类型,这里可以使用别名 24 注意:这里虽然编译器没有autoMapping的联想提示,手动设置这里会红色报错,但可以忽视,可能是dtd没有一更新而已! 25 --> 26 <association property="user" javaType="User" autoMapping="true"> 27 <!-- 28 user_id为user对象在表中的id列名:user_id, 29 property属性为user对象中的id字段名 30 --> 31 <id column="user_id" property="id"/> 32 </association> 33 </resultMap>
注意:
1、使用resultMap时,要将resultType="OrderUser" 改为:resultMap="OrderResultMap"
2、手动开启自动映射
3、在映射对象时,可以强制手动设置自动映射功能(虽然会有红线错误,无视)
7.2 一对多查询
需求:查询订单,查询出下单人信息并且查询出订单详情。
7.2.1 定义Statement
1 <select id="queryOrderAndUserAndOrderDetailByOrderNumber" parameterType="String" resultMap="OrderAndDetailResultMap"> 2 SELECT 3 o.*, 4 u.user_name, 5 u.name, 6 od.id detail_id, 7 od.item_id 8 FROM 9 tb_order o 10 LEFT JOIN tb_user u ON u.id = o.user_id 11 LEFT JOIN tb_orderdetail od ON od.order_id = o.id 12 WHERE o.order_number = #{orderNumber} 13 </select>
7.2.2 定义ResultMap
1 <!-- 查询订单,并且查询用户信息以及订单详情 --> 2 <resultMap type="Order" id="OrderAndDetailResultMap" autoMapping="true"> 3 <id column="id" property="id"/> 4 <result column="user_id" property="userId"/> 5 6 <association property="user" javaType="User" autoMapping="true"> 7 <!-- 对应到user对象中的id --> 8 <id column="user_id" property="id"/> 9 </association> 10 11 <!-- 聚合结果集 --> 12 <!-- javaType:order对象中的集合类型,ofType:集合中的对象类型 --> 13 <collection property="orderdetails" javaType="List" ofType="Orderdetail" autoMapping="true"> 14 <!--detail_id为statement中,给od.id起的别名 --> 15 <id column="detail_id" property="id"/> 16 </collection> 17 </resultMap>
注意:javaType和ofType的区别;
7.3 多对多查询
查询订单,查询出下单人信息并且查询出订单详情中的商品数据。
7.3.1.定义Statement
1 <select id="queryOrderAndUserAndOrderDetailAndItemByOrderNumber" parameterType="String" resultMap="OrderAndDetailAndItemResultMap"> 2 SELECT 3 o.*, 4 u.user_name, 5 u.name, 6 od.id detail_id, 7 od.item_id, 8 i.item_name, 9 i.item_price, 10 i.item_detail 11 FROM 12 tb_order o 13 LEFT JOIN tb_user u ON u.id = o.user_id 14 LEFT JOIN tb_orderdetail od ON od.order_id = o.id 15 LEFT JOIN tb_item i ON od.item_id = i.id 16 WHERE o.order_number = #{orderNumber} 17 </select>
7.3.2.定义ResultMap
1 <!-- 查询订单,查询出下单人信息并且查询出订单详情中的商品数据。 (多对多) --> 2 <resultMap type="Order" id="OrderAndDetailAndItemResultMap" autoMapping="true"> 3 <id column="id" property="id"/> 4 <result column="user_id" property="userId"/> 5 6 <association property="user" javaType="User" autoMapping="true"> 7 <!-- 对应到user对象中的id --> 8 <id column="user_id" property="id"/> 9 </association> 10 11 <!-- 聚合结果集 --> 12 <!-- javaType:order对象中的集合类型,ofType:集合中的对象类型 --> 13 <collection property="orderdetails" javaType="List" ofType="Orderdetail" autoMapping="true"> 14 <id column="detail_id" property="id"/> 15 16 <!-- 映射Item对象 --> 17 <association property="item" javaType="Item" autoMapping="true"> 18 <!-- 设置id --> 19 <id column="item_id" property="id"/> 20 </association> 21 </collection> 22 </resultMap>
7.4. ResultMap的继承
1 <!-- 查询订单并且查询用户信息 --> 2 <resultMap type="Order" id="OrderResultMap" autoMapping="true"> 3 <id column="id" property="id"/> 4 <result column="user_id" property="userId"/> 5 6 <association property="user" javaType="User" autoMapping="true"> 7 <!-- 对应到user对象中的id --> 8 <id column="user_id" property="id"/> 9 </association> 10 </resultMap> 11 12 <!-- 查询订单,并且查询用户信息以及订单详情 ,使用继承实现--> 13 <resultMap type="Order" id="OrderAndDetailResultMapExtends" autoMapping="true" extends="OrderResultMap"> 14 15 <!-- 聚合结果集 --> 16 <!-- javaType:order对象中的集合类型,ofType:集合中的对象类型 --> 17 <collection property="orderdetails" javaType="List" ofType="Orderdetail" autoMapping="true"> 18 <id column="detail_id" property="id"/> 19 </collection> 20 </resultMap>
extends中指定的是ResultMap的id;
8. 延迟加载
延迟加载的意义在于,虽然是关联查询,但不是及时将关联的数据查询出来,而且在需要的时候进行查询。
开启延迟加载:
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
lazyLoadingEnabled:true使用延迟加载,false禁用延迟加载。默认为true
aggressiveLazyLoading:true启用时,当延迟加载开启时访问对象中一个懒对象属性时,将完全加载这个对象的所有懒对象属性。false,当延迟加载时,按需加载对象属性(即访问对象中一个懒对象属性,不会加载对象中其他的懒对象属性)。默认为true
8.1.定义Statement
1 <select id="queryLazyOrderAndUserByOrderNumber" parameterType="String" resultMap="LazyOrderAndUserResultMap"> 2 SELECT * FROM tb_order WHERE order_number = #{orderNumber} 3 </select>
8.2.定义ResultMap
1 <!-- 延迟加载用户信息(一对一查询) --> 2 <resultMap type="Order" id="LazyOrderAndUserResultMap" autoMapping="true"> 3 <id column="id" property="id"/> 4 <result column="user_id" property="userId"/> 5 6 <!-- 7 select表延迟加载用户数据,column为两表的关系列, 8 注:select正常情况下直接用StatementId即可,这里用了跨命名空间引用statment 9 --> 10 <association property="user" javaType="User" column="user_id" select="cn.itcast.mybatis.mapper.UserMapper.queryUserById"> 11 </association> 12 </resultMap>
小知识点:
Statement是可以跨命名空间使用,使用方法:命名空间.StatementId
9.Mybatis Generator 代码生成器
实际中,用这个比较少!用这个有风险,因为自动生成的代码轻易不可改动,而且生成的虽然比较全,但是好多是我们不需要的!后边会介绍一个定制化的代码生成器,那个用的比较多!
9.1 核心配置文件generatorConfig.xml
在resources下创建generatorConfig.xml,内容如下:
需要修改的配置项为:
- 数据库连接配置
- 生成实体类的路径,注意,这里的路径相对于项目的根路径
- 生成mapperxml的路径
- 生成mapper接口的路径
- 配置数据库表信息
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE generatorConfiguration 3 PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" 4 "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> 5 6 <generatorConfiguration> 7 <context id="testTables" targetRuntime="MyBatis3"> 8 <commentGenerator> 9 <!-- 是否去除自动生成的注释 true:是 : false:否 --> 10 <property name="suppressAllComments" value="true" /> 11 </commentGenerator> 12 <!--数据库连接的信息:驱动类、连接地址、用户名、密码 --> 13 <jdbcConnection driverClass="com.mysql.jdbc.Driver" 14 connectionURL="jdbc:mysql://localhost:3306/mybatis" userId="root" 15 password="123456"> 16 </jdbcConnection> 17 18 <!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL 19 和 NUMERIC 类型解析为java.math.BigDecimal --> 20 <javaTypeResolver> 21 <property name="forceBigDecimals" value="false" /> 22 </javaTypeResolver> 23 24 <!-- targetProject:生成POJO类的位置 --> 25 <javaModelGenerator targetPackage="cn.itcast.mybatis.pojo" 26 targetProject="src/main/java"> 27 <!-- enableSubPackages:是否让schema作为包的后缀 --> 28 <property name="enableSubPackages" value="false" /> 29 <!-- 从数据库返回的值被清理前后的空格 --> 30 <property name="trimStrings" value="true" /> 31 </javaModelGenerator> 32 <!-- targetProject:mapper映射文件生成的位置 --> 33 <sqlMapGenerator targetPackage="cn.itcast.mybatis.mapper" 34 targetProject="src/main/java"> 35 <!-- enableSubPackages:是否让schema作为包的后缀 --> 36 <property name="enableSubPackages" value="false" /> 37 </sqlMapGenerator> 38 <!-- targetPackage:mapper接口生成的位置 --> 39 <javaClientGenerator type="XMLMAPPER" 40 targetPackage="cn.itcast.mybatis.mapper" targetProject="src/main/java"> 41 <!-- enableSubPackages:是否让schema作为包的后缀 --> 42 <property name="enableSubPackages" value="false" /> 43 </javaClientGenerator> 44 <!-- 指定数据库表 --> 45 <table schema="user" tableName="tb_user"></table> 46 </context> 47 </generatorConfiguration>
9.2 运行(执行器)
1 public static void main(String[] args) throws Exception { 2 List<String> warnings = new ArrayList<String>(); 3 boolean overwrite = true; 4 File configFile = Resources.getResourceAsFile("generatorConfig.xml"); 5 ConfigurationParser cp = new ConfigurationParser(warnings); 6 Configuration config = cp.parseConfiguration(configFile); 7 DefaultShellCallback callback = new DefaultShellCallback(overwrite); 8 MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings); 9 myBatisGenerator.generate(null); 10 }