MyBatis
什么是 MyBatis?
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。
MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和
Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
简单入门
<dependencies>
<!--MyBatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
</dependencies>
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--核心配置文件-->
<configuration>
<settings>
<setting name="mapUnderscoreToCamelCase" value="false"/>
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/数据库名?useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="数据库用户名"/>
<property name="password" value="密码"/>
</dataSource>
</environment>
</environments>
<!--每一个mapper.xml 都需要在Mybatis核心配置文件中注册 -->
<mappers>
<mapper class="com.test.dao.UserMapper"/>
</mappers>
</configuration>
创建实体类
public class User {
private Integer id;
private String userName;
private Date birthday;
private String sex;
private String address;
public User() {
}
public User(Integer id, String userName, Date birthday, String sex, String address) {
this.id = id;
this.userName = userName;
this.birthday = birthday;
this.sex = sex;
this.address = address;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", userName='" + userName + '\'' +
", birthday=" + birthday +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
}
持久化层
public interface UserMapper {
/**
* 查询所有
* @return
*/
List<User> queryAllUSer();
/**
* 通过名字查询
* @param name
* @return
*/
List<User> queryUserByName(String name);
/**
* 插入用户
* @param user
*/
void insertUser(User user);
/**
* 更新用户
* @param user
*/
void updateUser(User user);
/**
* 删除用户
* @param id
*/
void deleteUser(Integer id);
}
编写sql语句
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace=绑定一个对应的Dao/Mapper 接口-->
<mapper namespace="com.test.dao.UserMapper">
<select id="queryAllUSer" resultType="com.test.pojo.User">
select * from mybatis1.user
</select>
<select id="queryUserByName" parameterType="String" resultType="com.test.pojo.User">
select * from user where username=#{name}
</select>
<insert id="insertUser" parameterType="com.test.pojo.User">
insert into user (id,username,birthday,sex,address) values(#{id},#{userName},#{birthday},#{sex},#{address})
</insert>
<update id="updateUser" parameterType="com.test.pojo.User">
update user set username=#{userName},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}
</update>
<delete id="deleteUser" parameterType="Integer">
delete from user where id=#{id}
</delete>
</mapper>
Mybatis工具类
//SqlSessionFactory --->> Sqlsession 工厂模式
public class MybatisUtil {
private static SqlSessionFactory sqlSessionFactory;
static{
try {
//使用Mybatis 第一步 获取SQL Session Factory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource); //字节流 reader字符流
// Reader reader = Resources.getResourceAsReader(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
// 既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例
// SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
测试
public class MyTest {
@Test
public void getUserLIst(){
//获取sqlSession 从sqlSession工厂中
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.queryAllUSer();
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
@Test
public void getUserByName(){
//获取sqlSession 从sqlSession工厂中
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.queryUserByName("喜羊羊");
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
@Test
public void insertUser(){
//获取sqlSession 从sqlSession工厂中
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.insertUser(new User(7,"沸羊羊",new Date(),"男","羊村"));
System.out.println(mapper.queryUserByName("沸羊羊"));
//增删改必须 提交事务
sqlSession.commit();
sqlSession.close();
}
@Test
public void updateUser(){
//获取sqlSession 从sqlSession工厂中
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.updateUser(new User(7,"舔狗",new Date(),"男","羊村"));
//增删改必须 提交事务
sqlSession.commit();
sqlSession.close();
}
@Test
public void deleteUser(){
//获取sqlSession 从sqlSession工厂中
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.deleteUser(7);
//增删改必须 提交事务
sqlSession.commit();
sqlSession.close();
}
}
在进行增删改操作时要提交事务,否则数据库信息不会变更,可以在设置openSesion(true)自动提交
配置configuration
必须按照这个顺序进行配置
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/数据库?useUnicode=true&characterEncoding=UTF-8
username=username
password=password
<!--先映入外部文件db.properties 才能使用-->
<properties resource="db.properties"/>
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
<settings>
<!-- 标准的日志工厂实现 -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<setting name="logImpl" value="LOG4J"/>
<!-- 显示 开启全局缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
第一个日志开启会在控制台打印输出执行的sql日志. 使用第二个LOG4J时要添加依赖,创建log4j.properties文件
##将等级为DEBUG的日志信思输出到console控制台和file文件这两个目的地,console和fiLe的定义在下面的代码
log4j.rootLogger=DEBUG,console,file
#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
#输出方式
log4j.appender.console.Target = System.out
#输出级别
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
#输出格式
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
#输出文件的目录
log4j.appender.file.File=./log/mybatis.log
#输出文件的最大存储量
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
缓存
要在映射文件中,应用二级缓存
<cache eviction='FIFO' flushInterval='60000' size='512' readOnly='true'/>
<typeAliases>
<typeAlias alias="User" type="com.test.pojo.User"/>
</typeAliases>
alias时别名,在写sql标签时中需要用到parameterType和resultType就可以使用别名。 也可以用package标签,MyBatis会在包名下面搜索需要的Java Bean。也可以用注解方式 @Alias("author")
<package name="com.test.pojo"/>
public class MybatisUtil {
private static SqlSessionFactory sqlSessionFactory;
static{
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<!-- 使用相对于类路径的资源引用 -->
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<!-- 使用完全限定资源定位符(URL) -->
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mapper class="org.mybatis.builder.AuthorMapper"/>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<package name="org.mybatis.builder"/>
</mappers>
xml映射器
MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单 MyBatis 致力于减少使用成本,让用户能更专注于SQL 代码。 增删改查主要对应 insert、delete、update、select标签 用select标签举例:
<select id="queryAllUSer" resultType="com.test.pojo.User">
select * from mybatis1.user
</select>
<select id="queryUserByName" parameterType="String" resultType="com.test.pojo.User">
select * from user where username=#{name}
</select>
id属性后满是持久化层接口中的方法名且必须一致。
parameterType将会传入这条语句的参数的类全限定名或别名。 这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置。 并且在传递多个参数或者类型不同的参数时可以使用map进行传参、或者是基于注解@Param()实现。 还有就是当数据库字段类型比较特殊的情况下使用 {property,javaType=int,jdbcType=NUMERIC} 指定一个特殊的数据类型。但是目前没有遇到需要使用的地方,但是应该也很重要吧。
resultType是返回的结果的限定名或别名。也可以使用resultMap结果集映射,通常在数据库字段名和实体类之端明不宜这情况下使用,个人感觉非常好用~
sql语句也可以写在持久化层中,在方法的上面加上@select、@insert、@update、@delete注解方式直接编写功能简单的sql。
动态sql
当面临可选择的条件查询时,就比如在我的博客后台中根据博客各种属性进行选择性的查询情况下使用。 if标签中的test属性后就是查询条件,当根据性别选项不为空的查询时就会在 sql语句后加上if中的条件。
List<User> getUserListByIF(@Param("address") String address,@Param("sex") String sex);
<select id="getUserListByIF" resultType="user">
select * from user where address=#{address}
<if test="sex!= null">
and sex=#{sex}
</if>
</select>
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用,就像java中的switch语句一样。 当when标签中的条件成立时,就只将when标签中的sql拼接起来,否者就拼接otherwise中的slq语句。
@Test
public void selectByCWO(){
//获取sqlSession 从sqlSession工厂中
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Object> map = new HashMap<>();
map.put("address","羊村");
// map.put("sex","男");
mapper.getUserListByCWO(map);
sqlSession.close();
}
<select id="getUserListByCWO" resultType="user">
select * from user where address=#{address}
<choose>
<when test="sex!=null">
and sex=#{sex}
</when>
<otherwise>
and age>17
</otherwise>
</choose>
</select>
在xml中id in和Java中得到增强for循环一样使用,在foreach标签中将map、list等传进collection,item是当前迭代对象,index是下标索引,item值,index是键。 open="(" 是在slq中的in后添加‘(’,separator=","是以‘,’进行分割,close=")"以‘)’为结尾 完整的sql语句就成了:select * from user where id in (item1,item2,item3…)。
<select id="selectForeach" resultType="user">
select * from user where id in
<foreach item="item" index="index" collection="list" open="(" separator="," close=")">
#{item}
</foreach>
</select>
<delete id="deleteForeach">
delete from user where id in
<foreach item="id" index="index" collection="list" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<insert id="insertForeach">
insert into user (id,username,age,birthday,sex,address) value
<foreach item="user" index="index" collection="list" separator=",">
(#{user.id},#{user.userName},#{user.age},#{user.birthday},#{user.sex},#{user.address})
</foreach>
</insert>
底层原理浅析
每一个基于MyBatis应用的都是以一个SqlSessionFactory的实例为核心,而SqlSessionFactory是通过SqlSessionFactoryBuilder从xml配置文件中构建出来的。 在MyBatis中有一个Resounces工具类,它能够更好的帮助MyBatis解析xml文件。 再来看SqlSessionFactoryBuilder是如何build出SqlSessionFactory的。创造一个SqlSessionFactoryBuilder调用build,通过重载本类的build方法下面的的第一个build方法, 将读取的xml流放进XMLConfigBuilder构造方法中,实例出一个XMLConfigBuilder对象,在经过最下面的重载build方法实例出一个DefaultSqlSessionFactory, 这个DefaultSqlSessionFactory就成了最终的SqlSessionFactory。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException var13) {
}
}
return var5;
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
那就再看看XMLConfigBuilder是如何解析xml的,在构造器中又构造了父类Configuration, 从中又看到了一些熟悉的字眼,应该就是在这一步将xml文件配置进行了解析存储进了configuration, 然后再将configuration给了DefaultSqlSessionFactory。
private boolean parsed;
private final XPathParser parser;
private String environment;
private final ReflectorFactory localReflectorFactory;
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
this.localReflectorFactory = new DefaultReflectorFactory();
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
protected Environment environment;
protected boolean safeRowBoundsEnabled;
protected boolean safeResultHandlerEnabled;
protected boolean mapUnderscoreToCamelCase;
protected boolean aggressiveLazyLoading;
protected boolean multipleResultSetsEnabled;
protected boolean useGeneratedKeys;
protected boolean useColumnLabel;
protected boolean cacheEnabled;
protected boolean callSettersOnNulls;
protected boolean useActualParamName;
protected boolean returnInstanceForEmptyRow;
protected String logPrefix;
protected Class<? extends Log> logImpl;
protected Class<? extends VFS> vfsImpl;
protected LocalCacheScope localCacheScope;
protected JdbcType jdbcTypeForNull;
protected Set<String> lazyLoadTriggerMethods;
protected Integer defaultStatementTimeout;
protected Integer defaultFetchSize;
protected ResultSetType defaultResultSetType;
protected ExecutorType defaultExecutorType;
protected AutoMappingBehavior autoMappingBehavior;
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior;
protected Properties variables;
protected ReflectorFactory reflectorFactory;
protected ObjectFactory objectFactory;
protected ObjectWrapperFactory objectWrapperFactory;
protected boolean lazyLoadingEnabled;
protected ProxyFactory proxyFactory;
protected String databaseId;
protected Class<?> configurationFactory;
protected final MapperRegistry mapperRegistry;
protected final InterceptorChain interceptorChain;
protected final TypeHandlerRegistry typeHandlerRegistry;
protected final TypeAliasRegistry typeAliasRegistry;
protected final LanguageDriverRegistry languageRegistry;
protected final Map<String, MappedStatement> mappedStatements;
protected final Map<String, Cache> caches;
protected final Map<String, ResultMap> resultMaps;
protected final Map<String, ParameterMap> parameterMaps;
protected final Map<String, KeyGenerator> keyGenerators;
protected final Set<String> loadedResources;
protected final Map<String, XNode> sqlFragments;
protected final Collection<XMLStatementBuilder> incompleteStatements;
protected final Collection<CacheRefResolver> incompleteCacheRefs;
protected final Collection<ResultMapResolver> incompleteResultMaps;
protected final Collection<MethodResolver> incompleteMethods;
protected final Map<String, String> cacheRefMap;
public Configuration() {
this.safeResultHandlerEnabled = true;
this.multipleResultSetsEnabled = true;
this.useColumnLabel = true;
this.cacheEnabled = true;
this.useActualParamName = true;
this.localCacheScope = LocalCacheScope.SESSION;
this.jdbcTypeForNull = JdbcType.OTHER;
this.lazyLoadTriggerMethods = new HashSet(Arrays.asList("equals", "clone", "hashCode", "toString"));
this.defaultExecutorType = ExecutorType.SIMPLE;
this.autoMappingBehavior = AutoMappingBehavior.PARTIAL;
this.autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
this.variables = new Properties();
this.reflectorFactory = new DefaultReflectorFactory();
this.objectFactory = new DefaultObjectFactory();
this.objectWrapperFactory = new DefaultObjectWrapperFactory();
this.lazyLoadingEnabled = false;
this.proxyFactory = new JavassistProxyFactory();
this.mapperRegistry = new MapperRegistry(this);
this.interceptorChain = new InterceptorChain();
this.typeHandlerRegistry = new TypeHandlerRegistry();
this.typeAliasRegistry = new TypeAliasRegistry();
this.languageRegistry = new LanguageDriverRegistry();
this.mappedStatements = (new Configuration.StrictMap("Mapped Statements collection")).conflictMessageProducer((savedValue, targetValue) -> {
return ". please check " + savedValue.getResource() + " and " + targetValue.getResource();
});
this.caches = new Configuration.StrictMap("Caches collection");
this.resultMaps = new Configuration.StrictMap("Result Maps collection");
this.parameterMaps = new Configuration.StrictMap("Parameter Maps collection");
this.keyGenerators = new Configuration.StrictMap("Key Generators collection");
this.loadedResources = new HashSet();
this.sqlFragments = new Configuration.StrictMap("XML fragments parsed from previous mappers");
this.incompleteStatements = new LinkedList();
this.incompleteCacheRefs = new LinkedList();
this.incompleteResultMaps = new LinkedList();
this.incompleteMethods = new LinkedList();
this.cacheRefMap = new HashMap();
this.typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
this.typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
this.typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
this.typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
this.typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
this.typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
this.typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
this.typeAliasRegistry.registerAlias("LRU", LruCache.class);
this.typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
this.typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
this.typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
this.typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
this.typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
this.typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
this.typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
this.typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
this.typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
this.typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
this.typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
this.typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
this.typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
this.typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
this.languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
this.languageRegistry.register(RawLanguageDriver.class);
}
生命周期和作用域
SqlSessionFactoryBuilder:
- 一旦创建了 SqlSessionFactory,就不再需要它了。
- 局部变量
SqlSessionFactory:
- 说白就是可以想象为:数据库连接池。
- SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
- SqlSessionFactory 的最佳作用域是应用作用域。
- 最简单的就是使用单例模式或者静态单例模式。
SqlSession:
- 连接到连接池的一个请求!
- SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
- 用完后需要赶紧关闭,否则资源被占用!