文章转载自:http://www.itsoku.com/article/248
Mybatis系列目标:从入门开始开始掌握一个高级开发所需要的Mybatis技能。
这是mybatis系列第4篇。
主要内容
- idea创建本篇案例
- 建库建表
- 创建项目
- 别名使用详解(typeAliases)
- 为什么需要使用别名
- 别名3种用法详解
- 方式1:使用typeAlias元素注册别名
- 方式2:使用package元素批量注册别名
- 方式3:使用package结合@Alias批量注册并指定别名的名称
- 别名不区分大小写
- mybatis内置的别名
- 别名的原理
- 别名使用建议
- 属性配置详解(properties)
- 属性配置的3种方式
- 方式1:通过propertie元素配置属性
- 方式2:方式通过resource引用classpath中的属性配置文件
- 方式3:通过url引用外部属性配置文件
- 使用建议
- 相关问题
- mybatis中引入mapper的3种方式
- 方式1:通过mapper元素resource属性的方式注册Mapper xml文件和Mapper接口
- 方式2:通过mapper元素class属性的方式注册Mapper接口和Mapper xml文件
- 方式3:通过package元素批量注册Mapper接口和Mapper xml文件
- 源码解释
- 使用注意
idea创建案例
建库建表
/*创建数据库javacode2018*/
DROP DATABASE IF EXISTS `javacode2018`;
CREATE DATABASE `javacode2018`;
USE `javacode2018`;
/*创建表结构*/
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE t_user (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键,用户id,自动增长',
`name` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '姓名',
`age` SMALLINT NOT NULL DEFAULT 1 COMMENT '年龄',
`salary` DECIMAL(12,2) NOT NULL DEFAULT 0 COMMENT '薪水',
`sex` TINYINT NOT NULL DEFAULT 0 COMMENT '性别,0:未知,1:男,2:女'
) COMMENT '用户表';
DROP TABLE IF EXISTS `t_order`;
CREATE TABLE t_order (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键,订单id,自动增长',
`user_id` BIGINT NOT NULL DEFAULT 0 COMMENT '用户id',
`price` DECIMAL(12,2) NOT NULL DEFAULT 0 COMMENT '订单金额'
) COMMENT '订单表';
/*插入几条测试数据*/
INSERT INTO t_user (`name`,`age`,`salary`,`sex`)
VALUES
('路人甲Java',30,50000,1),
('javacode2018',30,50000,1),
('张学友',56,500000,1),
('林志玲',45,88888.88,2);
INSERT INTO t_order (`user_id`,`price`)
VALUES
(1,88.88),
(2,666.66);
SELECT * FROM t_user;
SELECT * FROM t_order;
创建工程
整个mybatis系列的代码采用maven模块的方式管理的,可以在文章底部获取,本次我们还是在上一篇的mybatis-series
中进行开发,在这个项目中新建一个模块chat03
,模块坐标如下:
com.javacode2018
chat03
1.0-SNAPSHOT
下面我们通过mybatis快速来实现对t_user表增删改查,这个在上一篇的chat02
中已经详细讲解过了。
创建mybatis配置文件
chat03\src\main\resources\demo1目录创建,mybatis-config.xml,如下:
创建UserMapper.xml文件
chat03\src\main\resources\demo1\mapper
目录创建,UserMapper.xml
,如下:
创建UserModel类
chat03\src\main\java\com\javacode2018\chat03\demo1
目录创建UserModel.java
,如下:
package com.javacode2018.chat03.demo1;
import lombok.*;
/**
* 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!
*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public class UserModel {
private Long id;
private String name;
private Integer age;
private Double salary;
private Integer sex;
}
创建UserMapper接口
chat03\src\main\java\com\javacode2018\chat03\demo1
目录创建UserMapper.java
,如下:
package com.javacode2018.chat03.demo1;
import java.util.List;
/**
* 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!
*/
public interface UserMapper {
int insertUser(UserModel model);
int updateUser(UserModel model);
int deleteUser(Long userId);
List getUserList();
}
引入logback日志支持
chat03\src\main\resources
目录创建logback.xml
,如下:
%d{mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
创建测试用例UserMapperTest
chat03\src\test\java\com\javacode2018\chat03\demo1
目录创建UserMapperTest.java
,如下:
package com.javacode2018.chat03.demo1;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!
*/
@Slf4j
public class UserMapperTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void before() throws IOException {
//指定mybatis全局配置文件
String resource = "demo1/mybatis-config.xml";
//读取全局配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
//构建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
this.sqlSessionFactory = sqlSessionFactory;
}
@Test
public void getUserList() {
try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//执行查询操作
List userModelList = mapper.getUserList();
userModelList.forEach(item -> {
log.info("{}", item);
});
}
}
}
代码解释一下:
上面的
before()
方法上面有个@Before
注解,这个是junit提供的一个注解,通过junit运行每个@Test
标注的方法之前,会先运行被@before
标注的方法,before()
方法中我们创建了SqlSessionFactory
对象,所以其他的@Test
标注的方法中可以直接使用sqlSessionFactory
对象了。
项目结构如下图
注意项目结构如下图,跑起来有问题的可以对照一下。
运行一下测试用例看效果
运行一下UserMapperTest.getUserList()
方法,输出如下:
32:21.991 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - ==> Preparing: SELECT * FROM t_user
32:22.028 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - ==> Parameters:
32:22.052 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - <== Total: 4
32:22.053 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)
32:22.056 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1)
32:22.056 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=3, name=张学友, age=56, salary=500000.0, sex=1)
32:22.056 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=4, name=林志玲, age=45, salary=88888.88, sex=2)
上面是mybatis开发项目的一个玩转的步骤,希望大家都能够熟练掌握,下面我们来在这个示例的基础上讲解本章的知识点。
别名
为什么需要使用别名?
大家打开chat03\src\main\resources\demo1\mapper\UserMapper.xml文件看一下,是不是有很多下面这样的代码:
parameterType="com.javacode2018.chat03.demo1.UserModel"
resultType="com.javacode2018.chat03.demo1.UserModel"
parameterType是指定参数的类型,resultType是指定查询结果返回值的类型,他们的值都是UserModel
类完整的类名,比较长,mybatis支持我们给某个类型起一个别名,然后通过别名可以访问到指定的类型。
别名的用法
使用别名之前需要先在mybatis中注册别名,我们先说通过mybatis全局配置文件中注册别名,通过mybatis配置文件注册别名有3种方式。
方式1
使用typeAlias元素进行注册
如下:
typeAliases元素中可以包含多个typeAlias子元素,每个typeAlias可以给一个类型注册别名,有2个属性需要指定:
type:完整的类型名称
alias:别名
如上面给UserModel
起了一个别名为user
。
案例
给UserModel注册一个别名user
chat03\src\main\resources\demo1\mapper\UserMapper.xml中加入下面配置:
UserMapper.xml中使用别名,将chat03\src\main\resources\demo1\mapper\UserMapper.xml
中getUserList的resultType
的值改为user
,如下:
运行com.javacode2018.chat03.demo1.UserMapperTest#getUserList
,如下:
07:35.477 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - ==> Preparing: SELECT * FROM t_user
07:35.505 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - ==> Parameters:
07:35.527 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - <== Total: 4
07:35.527 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)
07:35.529 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1)
07:35.529 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=3, name=张学友, age=56, salary=500000.0, sex=1)
07:35.529 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=4, name=林志玲, age=45, salary=88888.88, sex=2)
看到了么,getUserList中我们使用的别名,运行是正常的,说明可以通过别名user
直接访问UserModel
。
方式2
通过packege元素批量注册
上面我们通过typeAlias元素可以注册一个别名,如果我们有很多类需要注册,需要写很多typeAlias配置。
mybatis为我们提供了批量注册别名的方式,通过package
元素,如下:
这个也是在typeAliases元素下面,不过这次使用的是package
元素,package有个name属性,可以指定一个包名,mybatis会加载这个包以及子包中所有的类型,给这些类型都注册别名,别名名称默认会采用类名小写的方式,如UserModel
的别名为usermodel
案例
下面我们将demo1/mybatis-config.xml
中typeAliases
元素的值改为下面这样:
mybatis会给com.javacode2018.chat03.demo1
包及子包中的所有类型注册别名,UserModel类在这个包中,会被注册,别名为usermodel
UserMapper.xml中使用别名,将chat03\src\main\resources\demo1\mapper\UserMapper.xml
中getUserList的resultType
的值改为usermodel
,如下:
上面我们将返回值的类型resultType的值改为了usermodel
我们来运行com.javacode2018.chat03.demo1.UserMapperTest#getUserList
,如下:
26:08.267 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - ==> Preparing: SELECT * FROM t_user
26:08.296 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - ==> Parameters:
26:08.318 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - <== Total: 4
26:08.319 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)
26:08.320 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1)
26:08.320 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=3, name=张学友, age=56, salary=500000.0, sex=1)
26:08.320 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=4, name=林志玲, age=45, salary=88888.88, sex=2)
看到了么,getUserList中我们使用的别名usermodel,运行也是正常的。
方式3
package结合@Alias批量注册并指定别名
方式2中通过package可以批量注册别名,如果指定的包中包含了多个类名相同的类,会怎么样呢?
我们在com.javacode2018.chat03.demo1.model
包中创建一个和UserModel同名的类,如下:
package com.javacode2018.chat03.demo1.model;
import lombok.*;
/**
* 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!
*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public class UserModel {
private Long id;
private String name;
private Integer age;
private Double salary;
private Integer sex;
}
现在com.javacode2018.demo1
包中有2个UserModel类了
运行com.javacode2018.chat03.demo1.UserMapperTest#getUserList
,如下:
org.apache.ibatis.exceptions.PersistenceException:
## Error building SqlSession.
## The error may exist in SQL Mapper Configuration
## Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: org.apache.ibatis.type.TypeException: The alias 'UserModel' is already mapped to the value 'com.javacode2018.chat03.demo1.model.UserModel'.
at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
at org.apache.ibatis.session.SqlSessionFactoryBuilder.build(SqlSessionFactoryBuilder.java:80)
at org.apache.ibatis.session.SqlSessionFactoryBuilder.build(SqlSessionFactoryBuilder.java:64)
at com.javacode2018.chat03.demo1.UserMapperTest.before(UserMapperTest.java:29)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: org.apache.ibatis.type.TypeException: The alias 'UserModel' is already mapped to the value 'com.javacode2018.chat03.demo1.model.UserModel'.
at org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration(XMLConfigBuilder.java:121)
at org.apache.ibatis.builder.xml.XMLConfigBuilder.parse(XMLConfigBuilder.java:98)
at org.apache.ibatis.session.SqlSessionFactoryBuilder.build(SqlSessionFactoryBuilder.java:78)
... 24 more
Caused by: org.apache.ibatis.type.TypeException: The alias 'UserModel' is already mapped to the value 'com.javacode2018.chat03.demo1.model.UserModel'.
at org.apache.ibatis.type.TypeAliasRegistry.registerAlias(TypeAliasRegistry.java:157)
at org.apache.ibatis.type.TypeAliasRegistry.registerAlias(TypeAliasRegistry.java:147)
at org.apache.ibatis.type.TypeAliasRegistry.registerAliases(TypeAliasRegistry.java:136)
at org.apache.ibatis.type.TypeAliasRegistry.registerAliases(TypeAliasRegistry.java:125)
at org.apache.ibatis.builder.xml.XMLConfigBuilder.typeAliasesElement(XMLConfigBuilder.java:164)
at org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration(XMLConfigBuilder.java:109)
... 26 more
报错了,2个类的类名一样了,默认都会使用usermodel
作为别名,别名重复了mybatis会报错,那么此时我们怎么办呢?
package方式批量注册别名的时候,我们可以给类中添加一个@Alias
注解来给这个类指定别名:
@Alias("user")
public class UserModel {
}
当mybatis扫描类的时候,发现类上有Alias
注解,会取这个注解的value
作为别名,如果没有这个注解,会将类名小写作为别名,如同方式2。
案例
我们在com.javacode2018.chat03.demo1.UserModel
类上加上下面注解:
@Alias("use")
public class UserModel {
}
修改demo1/mapper/UserMapper.xml
,将resultType
的值设置为user
:
再来运行com.javacode2018.chat03.demo1.UserMapperTest#getUserList
,如下:
18:51.219 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - ==> Preparing: SELECT * FROM t_user
18:51.250 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - ==> Parameters:
18:51.271 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - <== Total: 4
18:51.272 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)
18:51.274 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1)
18:51.274 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=3, name=张学友, age=56, salary=500000.0, sex=1)
18:51.274 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=4, name=林志玲, age=45, salary=88888.88, sex=2)
输出正常。
别名不区分大小写
我们可以将上面UserMapper.xml中的use
别名改成大写的:USER
,如下:
然后再运行一下com.javacode2018.chat03.demo1.UserMapperTest#getUserList
,如下:
42:49.474 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - ==> Preparing: SELECT * FROM t_user
42:49.509 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - ==> Parameters:
42:49.527 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - <== Total: 4
42:49.528 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)
42:49.530 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1)
42:49.530 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=3, name=张学友, age=56, salary=500000.0, sex=1)
42:49.531 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=4, name=林志玲, age=45, salary=88888.88, sex=2)
也是正常的,说明别名使用时是不区分大小写的。
mybatis内置的别名
mybatis默认为很多类型提供了别名,如下:
别名 | 对应的实际类型 |
---|---|
_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 |
上面这些默认都是在org.apache.ibatis.type.TypeAliasRegistry
类中进行注册的,这个类就是mybatis注册别名使用的,别名和具体的类型关联是放在这个类的一个map属性(typeAliases)中,贴一部分代码大家感受一下:
public class TypeAliasRegistry {
private final Map> typeAliases = new HashMap<>();
public TypeAliasRegistry() {
registerAlias("string", String.class);
registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
registerAlias("short", Short.class);
registerAlias("int", Integer.class);
registerAlias("integer", Integer.class);
registerAlias("double", Double.class);
registerAlias("float", Float.class);
registerAlias("boolean", Boolean.class);
registerAlias("byte[]", Byte[].class);
registerAlias("long[]", Long[].class);
registerAlias("short[]", Short[].class);
registerAlias("int[]", Integer[].class);
registerAlias("integer[]", Integer[].class);
registerAlias("double[]", Double[].class);
registerAlias("float[]", Float[].class);
registerAlias("boolean[]", Boolean[].class);
registerAlias("_byte", byte.class);
registerAlias("_long", long.class);
registerAlias("_short", short.class);
registerAlias("_int", int.class);
registerAlias("_integer", int.class);
registerAlias("_double", double.class);
registerAlias("_float", float.class);
registerAlias("_boolean", boolean.class);
registerAlias("_byte[]", byte[].class);
registerAlias("_long[]", long[].class);
registerAlias("_short[]", short[].class);
registerAlias("_int[]", int[].class);
registerAlias("_integer[]", int[].class);
registerAlias("_double[]", double[].class);
registerAlias("_float[]", float[].class);
registerAlias("_boolean[]", boolean[].class);
registerAlias("date", Date.class);
registerAlias("decimal", BigDecimal.class);
registerAlias("bigdecimal", BigDecimal.class);
registerAlias("biginteger", BigInteger.class);
registerAlias("object", Object.class);
registerAlias("date[]", Date[].class);
registerAlias("decimal[]", BigDecimal[].class);
registerAlias("bigdecimal[]", BigDecimal[].class);
registerAlias("biginteger[]", BigInteger[].class);
registerAlias("object[]", Object[].class);
registerAlias("map", Map.class);
registerAlias("hashmap", HashMap.class);
registerAlias("list", List.class);
registerAlias("arraylist", ArrayList.class);
registerAlias("collection", Collection.class);
registerAlias("iterator", Iterator.class);
registerAlias("ResultSet", ResultSet.class);
}
}
mybatis启动的时候会加载全局配置文件,会将其转换为一个org.apache.ibatis.session.Configuration
对象,存储在内存中,Configuration
类中也注册了一些别名,代码如下:
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
上面有2行如下:
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
上面这2行,注册了2个别名,别名和类型映射关系如下:
JDBC -> JdbcTransactionFactory
POOLED -> PooledDataSourceFactory
上面这2个对象,大家应该比较熟悉吧,mybatis全局配置文件(chat03\src\main\resources\demo1\mybatis-config.xml
)中我们用到过,我们再去看一下,如下:
上面2个红框的是不是就是上面注册的2个类型,上面xml中我们写的是完整类型名称,我们可以将其改为别名的方式也是可以的,如下:
我们来运行com.javacode2018.chat03.demo1.UserMapperTest#getUserList
,看一下能否正常运行,输出如下:
44:10.886 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - ==> Preparing: SELECT * FROM t_user
44:10.929 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - ==> Parameters:
44:10.947 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - <== Total: 4
44:10.948 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)
44:10.950 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1)
44:10.950 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=3, name=张学友, age=56, salary=500000.0, sex=1)
44:10.950 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=4, name=林志玲, age=45, salary=88888.88, sex=2)
很好,一切正常的。
别名的原理
mybatis允许我们给某种类型注册一个别名,别名和类型之间会建立映射关系,这个映射关系存储在一个map对象中,key为别名的名称,value为具体的类型,当我们通过一个名称访问某种类型的时候,mybatis根据类型的名称,先在别名和类型映射的map中按照key进行查找,如果找到了直接返回对应的类型,如果没找到,会将这个名称当做完整的类名去解析成Class对象,如果这2步解析都无法识别这种类型,就会报错。
mybatis和别名相关的操作都位于org.apache.ibatis.type.TypeAliasRegistry
类中,包含别名的注册、解析等各种操作。
我们来看一下别名解析的方法,如下:
public Class resolveAlias(String string) {
try {
if (string == null) {
return null;
}
// issue #748
String key = string.toLowerCase(Locale.ENGLISH);
Class value;
if (typeAliases.containsKey(key)) {
value = (Class) typeAliases.get(key);
} else {
value = (Class) Resources.classForName(string);
}
return value;
} catch (ClassNotFoundException e) {
throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e);
}
}
有一个typeAliases
对象,我们看一下其定义:
private final Map> typeAliases = new HashMap<>();
这个对象就是存放别名和具体类型映射关系的,从上面代码中可以看出,通过传入的参数解析对应的类型的时候,会先从typeAliases
中查找,如果找不到会调用下面代码:
value = (Class) Resources.classForName(string);
上面这个方法里面具体是使用下面代码去通过名称解析成类型的:
Class.forName(类名完整名称)
Class.forName大家应该是很熟悉的,可以获取一个字符串对应的Class对象,如果找不到这个对象,会报错。
别名使用建议
别名的方式可以简化类型的写法,原本很长一串的UserModel
对象,现在只用写个user
就行了,用起来是不是挺爽的?
从写法上面来说,确实少帮我们省了一些代码,但是从维护上面来讲,不是很方便。
如Mapper xml直接写别名,看代码的时候,很难知道这个别名对应的具体类型,还需要我们去注册的地方找一下,不是太方便,如果我们在idea中写完整的类名,还可以按住Ctrl
健,然后用鼠标左键点击类型
直接可以跳到对应的类定义中去,如果使用别名是无法导航过去的。
整体上来说开发和看代码都不是太方便,只是写法上比价简单。
所以建议自定义的类尽量别使用别名,而对mybatis中内置的一些别名我们需要知道。
属性配置文件详解
大家看一下chat03\src\main\resources\demo1\mybatis-config.xml中下面这一部分的配置:
这个连接数据库的配置,我们是直接写在mybatis全局配置文件中的,上面这是我们本地测试库的db信息,上线之后,需要修改为线上的db配置信息,db配置信息一般由运维去修改,让运维去修改这个xml配置文件?
这样不是太好,我们通常将一些需要运维修改的配置信息(如:db配置、邮件配置、redis配置等等各种配置)放在一个properties配文件中,然后上线时,只需要运维去修改这个配置文件就可以了,根本不用他们去修改和代码相关的文件。
mybatis也支持我们通过外部properties文件来配置一些属性信息。
mybatis配置属性信息有3种方式。
方式1:property元素中定义属性
属性定义
mybatis全局配置文件中通过properties元素来定义属性信息,如下:
上面通过property元素的方式进行配置属性信息:
name:属性的名称
value:属性的值。
如:
使用${属性名称}引用属性的值
属性已经定义好了,我们可以通过${属性名称}
引用定义好的属性的值,如:
案例
我们在demo1/mapper/mybatis-config.xml
的configuration
元素中加入下面配置:
修改datasource
的配置:
运行com.javacode2018.chat03.demo1.UserMapperTest#getUserList
,如下:
40:22.274 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - ==> Preparing: SELECT * FROM t_user
40:22.307 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - ==> Parameters:
40:22.330 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - <== Total: 4
40:22.331 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)
40:22.332 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1)
40:22.332 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=3, name=张学友, age=56, salary=500000.0, sex=1)
40:22.332 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=4, name=林志玲, age=45, salary=88888.88, sex=2)
运行正常。
方式2:resource引入配置文件
方式1中,我们的配置文件还是写在全局配置文件中,mybatis支持从外部引入配置文件,可以把配置文件写在其他外部文件中,然后进行引入。
引入classes路径中的配置文件
properties元素有个resource
属性,值为配置文件相对于classes的路径
,配置文件我们一般放在src/main/resource
目录,这个目录的文件编译之后会放在classes
路径中。
案例
下面我们将上面db的配置放在外部的config.properties文件中。
在chat03\src\main\resources\demo1
目录新建一个配置文件config.properties
,内容如下:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
jdbc.username=root
jdbc.password=root123
demo1/mapper/mybatis-config.xml
中引入上面配置文件:
目前demo1/mapper/mybatis-config.xml
文件内容如下:
运行com.javacode2018.chat03.demo1.UserMapperTest#getUserList
,如下:
57:40.405 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - ==> Preparing: SELECT * FROM t_user
57:40.436 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - ==> Parameters:
57:40.454 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - <== Total: 4
57:40.455 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)
57:40.457 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1)
57:40.457 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=3, name=张学友, age=56, salary=500000.0, sex=1)
57:40.457 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=4, name=林志玲, age=45, salary=88888.88, sex=2)
运行正常。
方式3:url的方式引入远程配置文件
mybatis还提供了引入远程配置文件的方式,如下:
这次还是使用properties
元素,不过使用的是url
属性,如:
这种方式的案例就不提供了,有兴趣的可以自己去玩玩。
属性配置文件使用建议
上面我们说了3种方式,第2种方式是比较常见的做法,建议大家可以使用第二种方式来引入外部资源配置文件。
问题
如果3种方式如果我们都写了,mybatis会怎么走?
下面我们修改一下resources/demo1/mybatis-config.xml
,使用第一种方式定义属性,如下:
将password
的值改为了root
,正确的是root123
,运行测试用例,报错如下:
org.apache.ibatis.exceptions.PersistenceException:
## Error querying database. Cause: java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: YES)
## The error may exist in demo1/mapper/UserMapper.xml
## The error may involve com.javacode2018.chat03.demo1.UserMapper.getUserList
## The error occurred while executing a query
## Cause: java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: YES)
at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:149)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140)
at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:147)
at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:80)
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:93)
at com.sun.proxy.$Proxy6.getUserList(Unknown Source)
at com.javacode2018.chat03.demo1.UserMapperTest.getUserList(UserMapperTest.java:38)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: YES)
提示密码错误。
下面我们将第2种方式也加入,修改配置:
再运行一下测试用例,如下:
18:59.436 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - ==> Preparing: SELECT * FROM t_user
18:59.462 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - ==> Parameters:
18:59.481 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - <== Total: 4
18:59.482 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)
18:59.485 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1)
18:59.485 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=3, name=张学友, age=56, salary=500000.0, sex=1)
18:59.485 [main] INFO c.j.chat03.demo1.UserMapperTest - UserModel(id=4, name=林志玲, age=45, salary=88888.88, sex=2)
这次正常了。
可以看出方式1和方式2都存在的时候,方式2的配置会覆盖方式1的配置。
mybatis这块的源码在org.apache.ibatis.builder.xml.XMLConfigBuilder#propertiesElement
方法中,如下:
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
从上面代码中也可以看出,如果方式2和方式3都存在的时候,方式3会失效,mybatis会先读取方式1的配置,然后读取方式2或者方式3的配置,会将1中相同的配置给覆盖。
mybatis中引入mapper的3种方式
mapper xml文件是非常重要的,我们写的sql基本上都在里面,使用mybatis开发项目的时候,和mybatis相关的大部分代码就是写sql,基本上都是和mapper xml打交道。
编写好的mapper xml需要让mybatis知道,我们怎么让mybatis知道呢?
可以通过mybatis全局配置文件进行引入,主要有3种方式。
方式1:使用mapper resouce属性注册mapper xml文件
目前我们所涉及到的各种例子都是采用的这种方式,使用下面的方法进行引入:
再来说一下这种方式的一些注意点:
- 一般情况下面我,我们会创建一个和Mapper xml中namespace同名的Mapper接口,Mapper接口会和Mapper xml文件进行绑定
- mybatis加载mapper xml的时候,会去查找namespace对应的Mapper接口,然后进行注册,我们可以通过Mapper接口的方式去访问Mapper xml中的具体操作
- Mapper xml和Mapper 接口配合的方式是比较常见的做法,也是强烈建议大家使用的
方式2:使用mapper class属性注册Mapper接口
引入Mapper接口
mybatis全局配置文件中引入mapper接口,如下:
这种情况下,mybais会去加载class
对应的接口,然后还会去加载和这个接口同一个目录的同名的xml文件。
如:
上面这种写法,mybatis会自动去注册UserMapper
接口,还会去查找下面的文件:
com/javacode2018/chat03/demo1/UserMapper.xml
大家以后开发项目的时候估计也会看到这种写法,Mapper接口
和Mapper xml
文件放在同一个包中。
案例
下面我们重新创建一个案例,都放在demo2包中。
新建com.javacode2018.chat03.demo2.UserModel
,如下:
package com.javacode2018.chat03.demo2;
import lombok.*;
import org.apache.ibatis.type.Alias;
/**
* 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!
*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public class UserModel {
private Long id;
private String name;
private Integer age;
private Double salary;
private Integer sex;
}
新建com.javacode2018.chat03.demo2.UserMapper
,如下:
package com.javacode2018.chat03.demo2;
import java.util.List;
/**
* 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!
*/
public interface UserMapper {
List getUserList();
}
chat03\src\main\java\com\javacode2018\chat03\demo2
中创建UserMapper.xml
,如下:
下面重点来了。
创建mybatis全局配置文件,在chat03\src\main\resources\demo2
目录中创建mybatis-config.xml
,如下:
chat03\src\test\java
目录创建测试用例com.javacode2018.chat03.demo2.UserMapperTest
,如下:
package com.javacode2018.chat03.demo2;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!
*/
@Slf4j
public class UserMapperTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void before() throws IOException {
//指定mybatis全局配置文件
String resource = "demo2/mybatis-config.xml";
//读取全局配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
//构建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
this.sqlSessionFactory = sqlSessionFactory;
}
@Test
public void getUserList() {
try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//执行查询操作
List userModelList = mapper.getUserList();
userModelList.forEach(item -> {
log.info("{}", item);
});
}
}
}
注意这次上面使用的是demo2/mybatis-config.xml
配置文件。
我们先来看一下项目结构,4个文件:
注意一下UserMapper
接口所在的包中有个同名的UserMapper.xml
文件,这个如果按照方式2中所说的,会自动加载。
下面我们来运行一下com.javacode2018.chat03.demo2.UserMapperTest#getUserList
,输出:
org.apache.ibatis.binding.BindingException: Type interface com.javacode2018.chat03.demo2.UserMapper is not known to the MapperRegistry.
at org.apache.ibatis.binding.MapperRegistry.getMapper(MapperRegistry.java:47)
at org.apache.ibatis.session.Configuration.getMapper(Configuration.java:779)
at org.apache.ibatis.session.defaults.DefaultSqlSession.getMapper(DefaultSqlSession.java:291)
at com.javacode2018.chat03.demo2.UserMapperTest.getUserList(UserMapperTest.java:36)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
从输出中可以看到,UserMapper
找不到。
我们去看一下demo2/mybatis-config.xml
这个配置文件,这个文件中需要使用方式2引入UserMapper
接口,在demo2/mybatis-config.xml
中加入下面配置:
再运行一下,还是报错,如下,还是找不到对应的UserMapper:
org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.javacode2018.chat03.demo2.UserMapper.getUserList
at org.apache.ibatis.binding.MapperMethod$SqlCommand.(MapperMethod.java:235)
at org.apache.ibatis.binding.MapperMethod.(MapperMethod.java:53)
还是有问题,我们看一下target/classes
中demo2
包的内容,如下图:
编译之后的文件中少了UserMapper.xml
,这个和maven有关,maven编译src/java代码的时候,默认只会对java文件进行编译然后放在target/classes目录,需要在chat03/pom.xml
中加入下面配置:
${project.basedir}/src/main/java
**/*.xml
${project.basedir}/src/main/resources
**/*
最终chat03/pom.xml
内容如下:
mybatis-series
com.javacode2018
1.0-SNAPSHOT
4.0.0
chat03
org.mybatis
mybatis
mysql
mysql-connector-java
org.projectlombok
lombok
junit
junit
ch.qos.logback
logback-classic
${project.basedir}/src/main/java
**/*.xml
${project.basedir}/src/main/resources
**/*
加了这个之后UserMapper.xml
就会被放到target的classes中去了,如下图:
为什么maven中需要加上面配置,这块大家可以去看公众号中maven系列的文章,里面有详细介绍,maven的相关东西,后面还会经常用到,对这块不熟悉的,建议尽快把maven系列的所有文章都看一遍,以免后面学习的过程中掉队。
我们再次运行一下测试用例com.javacode2018.chat03.demo2.UserMapperTest#getUserList
,效果如下:
24:37.814 [main] DEBUG c.j.c.demo2.UserMapper.getUserList - ==> Preparing: SELECT * FROM t_user
24:37.852 [main] DEBUG c.j.c.demo2.UserMapper.getUserList - ==> Parameters:
24:37.875 [main] DEBUG c.j.c.demo2.UserMapper.getUserList - <== Total: 4
24:37.876 [main] INFO c.j.chat03.demo2.UserMapperTest - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)
24:37.879 [main] INFO c.j.chat03.demo2.UserMapperTest - UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1)
24:37.879 [main] INFO c.j.chat03.demo2.UserMapperTest - UserModel(id=3, name=张学友, age=56, salary=500000.0, sex=1)
24:37.879 [main] INFO c.j.chat03.demo2.UserMapperTest - UserModel(id=4, name=林志玲, age=45, salary=88888.88, sex=2)
这次正常了。
源码
方式2对应的源码大家可以去看下面这个方法:
org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement
方法中会去加载mapper
元素中class
属性指定的Mapper接口,然后进行注册,随后会在接口同目录中查找同名的mapper xml文件,将解析这个xml文件,如果mapper xml文件不存在,也不会报错,源码还是比较简单的,大家可以去看一下,加深理解。
方式3:使用package元素批量注册Mapper接口
批量注册Mapper接口
上面说2种方式都是一个个注册mapper的,如果我们写了很多mapper,是否能够批量注册呢?
mybatis提供了扫描包批量注册的方式,需要在mybatis全局配置文件中加入下面配置:
mybatis会扫描package元素中name属性指定的包及子包中的所有接口
,将其当做Mapper 接口
进行注册,所以一般我们会创建一个mapper
包,里面放Mapper接口
和同名的Mapper xml文件
。
大家来看一个案例,理解一下。
案例
这个案例中将对t_user、t_order
两个表进行查询操作,采用方式3中的package批量引入mapper 接口和xml文件。
所有代码放在demo3包中,大家先看下文件所在的目录:
创建UserModel
类,如下:
package com.javacode2018.chat03.demo3.model;
import lombok.*;
/**
* 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!
*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public class UserModel {
private Long id;
private String name;
private Integer age;
private Double salary;
private Integer sex;
}
创建OrderModel
类,如下:
package com.javacode2018.chat03.demo3.model;
import lombok.*;
/**
* 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!
*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public class OrderModel {
private Long id;
private Long user_id;
private Double price;
}
创建UserMapper
接口,如下:
package com.javacode2018.chat03.demo3.mapper;
import com.javacode2018.chat03.demo3.model.UserModel;
import java.util.List;
/**
* 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!
*/
public interface UserMapper {
List getList();
}
创建OrderMapper
接口,如下:
package com.javacode2018.chat03.demo3.mapper;
import com.javacode2018.chat03.demo3.model.OrderModel;
import com.javacode2018.chat03.demo3.model.UserModel;
import java.util.List;
/**
* 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!
*/
public interface OrderMapper {
List getList();
}
创建UserMapper.xml
,如下:
上面我们写了一个查询t_user数据的sql
创建OrderMapper.xml
,如下:
上面我们写了一个查询t_order数据的sql
创建resources/demo3/mybatis-config.xml
配置文件,如下:
注意这次我们使用
package
来让mybatis加载com.javacode2018.chat03.demo3.mapper
包下面所有的Mapper接口和Mapper xml文件。
创建测试用例Demo3Test
,如下:
package com.javacode2018.chat03.demo3;
import com.javacode2018.chat03.demo3.mapper.OrderMapper;
import com.javacode2018.chat03.demo3.mapper.UserMapper;
import com.javacode2018.chat03.demo3.model.OrderModel;
import com.javacode2018.chat03.demo3.model.UserModel;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!
*/
@Slf4j
public class Demo3Test {
private SqlSessionFactory sqlSessionFactory;
@Before
public void before() throws IOException {
//指定mybatis全局配置文件
String resource = "demo3/mybatis-config.xml";
//读取全局配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
//构建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
this.sqlSessionFactory = sqlSessionFactory;
}
@Test
public void test() {
try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//执行查询操作
List userModelList = userMapper.getList();
userModelList.forEach(item -> {
log.info("{}", item);
});
log.info("----------------------------------");
OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class);
//执行查询操作
List orderModelList = orderMapper.getList();
orderModelList.forEach(item -> {
log.info("{}", item);
});
}
}
}
运行com.javacode2018.chat03.demo3.Demo3Test#test
,输出如下:
48:39.280 [main] DEBUG c.j.c.d.mapper.UserMapper.getList - ==> Preparing: SELECT * FROM t_user
48:39.315 [main] DEBUG c.j.c.d.mapper.UserMapper.getList - ==> Parameters:
48:39.339 [main] DEBUG c.j.c.d.mapper.UserMapper.getList - <== Total: 4
48:39.340 [main] INFO c.j.chat03.demo3.Demo3Test - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)
48:39.343 [main] INFO c.j.chat03.demo3.Demo3Test - UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1)
48:39.343 [main] INFO c.j.chat03.demo3.Demo3Test - UserModel(id=3, name=张学友, age=56, salary=500000.0, sex=1)
48:39.343 [main] INFO c.j.chat03.demo3.Demo3Test - UserModel(id=4, name=林志玲, age=45, salary=88888.88, sex=2)
48:39.343 [main] INFO c.j.chat03.demo3.Demo3Test - ----------------------------------
48:39.344 [main] DEBUG c.j.c.d.mapper.OrderMapper.getList - ==> Preparing: SELECT * FROM t_order
48:39.345 [main] DEBUG c.j.c.d.mapper.OrderMapper.getList - ==> Parameters:
48:39.351 [main] DEBUG c.j.c.d.mapper.OrderMapper.getList - <== Total: 2
48:39.351 [main] INFO c.j.chat03.demo3.Demo3Test - OrderModel(id=1, user_id=1, price=88.88)
48:39.351 [main] INFO c.j.chat03.demo3.Demo3Test - OrderModel(id=2, user_id=2, price=666.66)
这种批量的方式是不是用着挺爽的,不过有点不是太好,mapper xml和mapper接口放在了一个目录中,目录中既有java代码又有xml文件,看起来也挺别扭的,其实你们可以这样:
一般我们将配置文件放在resource
目录,我们可以在resource
目录中创建下面子目录:
com/javacode2018/chat03/demo3/mapper
然后将com.javacode2018.chat03.demo3.mapper
中的2个xml文件移到上面新创建的目录中去,如下图:
在去运行一下com.javacode2018.chat03.demo3.Demo3Test#test
,输出如下:
56:22.669 [main] DEBUG c.j.c.d.mapper.UserMapper.getList - ==> Preparing: SELECT * FROM t_user
56:22.700 [main] DEBUG c.j.c.d.mapper.UserMapper.getList - ==> Parameters:
56:22.721 [main] DEBUG c.j.c.d.mapper.UserMapper.getList - <== Total: 4
56:22.722 [main] INFO c.j.chat03.demo3.Demo3Test - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)
56:22.725 [main] INFO c.j.chat03.demo3.Demo3Test - UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1)
56:22.725 [main] INFO c.j.chat03.demo3.Demo3Test - UserModel(id=3, name=张学友, age=56, salary=500000.0, sex=1)
56:22.725 [main] INFO c.j.chat03.demo3.Demo3Test - UserModel(id=4, name=林志玲, age=45, salary=88888.88, sex=2)
56:22.725 [main] INFO c.j.chat03.demo3.Demo3Test - ----------------------------------
56:22.727 [main] DEBUG c.j.c.d.mapper.OrderMapper.getList - ==> Preparing: SELECT * FROM t_order
56:22.727 [main] DEBUG c.j.c.d.mapper.OrderMapper.getList - ==> Parameters:
56:22.732 [main] DEBUG c.j.c.d.mapper.OrderMapper.getList - <== Total: 2
56:22.732 [main] INFO c.j.chat03.demo3.Demo3Test - OrderModel(id=1, user_id=1, price=88.88)
56:22.732 [main] INFO c.j.chat03.demo3.Demo3Test - OrderModel(id=2, user_id=2, price=666.66)
也是可以的。
源码
方式3的源码和方式2的源码在一个地方:
org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement
方法中会去扫描指定的包中所有的接口,会将接口作为Mapper接口进行注册,然后还会找这些接口同名的Xml文件,将其注册为Mapper xml文件,相对于对方式2循环的方式。
使用注意
方式3会扫描指定包中所有的接口,把这些接口作为Mapper接口进行注册,扫描到的类型只要是接口就会被注册,所以指定的包中通常我们只放Mapper接口,避免存放一些不相干的类或者接口。
关于配置和源码
本次讲解到的一些配置都是在mybatis全局配置文件中进行配置的,这些元素配置是有先后顺序的,具体元素是在下面的dtd文件中定义的:
http://mybatis.org/dtd/mybatis-3-config.dtd
建议大家去看一下这个dtd配置文件。
Mybatis解析这个配置文件的入口是在下面的方法中:
org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
代码的部分实现如下:
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
可以看到mybatis启动的时候会按顺序加载上面的标签,大家可以去看一下源码,研究一下,下篇继续深入mybatis其他知识点。
总结
- 掌握别名注册的3种方式,建议大家尽量少使用自定义别名
- 掌握属性配置3种方式
- 掌握mapper注册的3种方式及需要注意的地方