ORM
对象关系映射(Object Relational Mapping,简称ORM)是通过使用描述对象和数据库之间映射的元数据,将面向对象语言程序中的对象自动持久化到关系数据库中。
具体表现:
O - Java对象
R - 数据库表
M - 映射文件
一个Java类对应数据库中的一张表;一个Java对象对应数据库表中的一条数据;Java对象的属性对应数据库表的列。
而这种对应关系通过映射(Mapping)来实现
MyBatis入门
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。
第一个MyBatis程序
存在一张数据库表user,相当于ORM中的“R”,建表语句如下:
CREATE TABLE `user` (
id int(11) NOT NULL AUTO_INCREMENT,
username varchar(20) DEFAULT NULL,
password varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
第一步需要导入依赖
<!--mybatis的jar包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!--数据库驱动jar mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.18</version>
</dependency>
第二步创建一个配置文件"mybatis-config.xml"
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<!--事务管理,采用JDBC-->
<transactionManager type="JDBC"/>
<!--mybatis提供的简单数据源,也可以取值为JNDI,让容器管理数据源-->
<dataSource type="POOLED">
<!--数据库连接的参数,任何数据库框架都需要以下参数-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_db?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/young/mapper/UserMapper.xml"/>
</mappers>
</configuration>
mybatis-config.xml映射文件解析:
XML 配置文件中包含了对 MyBatis 系统的核心设置,包括获取数据库连接实例的数据源(DataSource)以及决定事务作用域和控制方式的事务
管理器(TransactionManager)。
environment元素体中包含了事务管理和连接池的配置。在environments标签中设置default属性指定默认使用的数据库连接环境(environment)
mappers 元素则包含了一组映射器(mapper),这些映射器的 XML 映射文件包含了 SQL 代码和映射定义信息。
创建实体类User,相当于ORM中的“O”
public class User {
//注意实体类中的属性名称需要与数据库表中列名一一对应
private Integer id;
private String username;
private String password;
//以下省略了所有成员变量的get和set方法......
}
创建文件"UserMapper.xml"映射文件作为ORM中的“M”
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace:命名空间,相当于package-->
<!--Sql语句编写在Mapper配置文件中被程序读取,
程序通过namespace.sqlid确认唯一的sql的引用,namespace通常和文件名保持一致-->
<mapper namespace="com.young.mapper.UserMapper">
<!--查询标签,属性id是sql在本文件中的唯一标识-->
<!--resultType是mybatis中的结果集映射,不管结果是一条数据还是多条数据resultType都配置为单条数据的类型-->
<select id="selectAll" resultType="com.young.model.User">
select * from user
</select>
</mapper>
测试类:
import com.alibaba.fastjson.JSON;
import com.young.model.User;
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.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class TestMapper {
@Test
public void testMapper() throws IOException {
//获取字节输入流,用于读取配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//通过session工厂构建器构建session工厂
//SqlSessionFactory可以获取SqlSession对象,只能是唯一存在的
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//SqlSession可以类比为javaWeb中的session,表示程序和数据库的一次交互
//可以与数据库建立连接并执行查询、更新等语句
//此对象需要每次操作都是一个新的对象,不能放在全局供多个线程共同使用
SqlSession session = sessionFactory.openSession();
//执行Sql语句,传递Sql的唯一标识,即namespace.Sqlid
//selectList将查询的结果封装为一个List
//mybatis会将数据库中查询出的列和java实体类的属性一一对应,否则该属性不被赋值,如果名称不一致,可以在配置文件中配置或者使用别名
List<User> list = session.selectList("com.young.mapper.UserMapper.selectAll");
System.out.println(JSON.toJSONString(list,true));
}
}
每个基于MyBatis的应用都是以一个SqlSessionFactory的实例为核心的,SqlSessionFactoryBuilder可以从XML配置文件或一个预先配置的
Configuration实例来构建出SqlSessionFactory实例。MyBatis提供了一个Resources工具类,使得从类路径或其它位置加载资源文件更加容易。
SqlSessionFactoryBuilder
这个类可以被实例化、使用和丢弃,一旦创建了SqlSessionFactory,就不再需要它了。 因此SqlSessionFactoryBuilder实例的最佳作用域
是方法作用域(也就是局部方法变量)。在程序中最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。
SqlSessionFactory
SqlSessionFactory一旦被创建就应该在应用的运行期间一直存在,不能丢弃它或重新创建另一个实例。 使用SqlSessionFactory的最佳实践是
在应用运行期间不要重复创建多次。因此SqlSessionFactory的最佳作用域是应用作用域。 可以使用单例模式来做到这一点。
SqlSession
每个线程都应该有它自己的SqlSession实例。SqlSession的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。也绝不能将 SqlSession 实例的引用放在任何类型的托
管作用域中,比如Servlet框架中的HttpSession。如果你现在正在使用一种Web框架,考虑将SqlSession放在一个和HTTP请求相似的作用域中。
换句话说,每次收到HTTP请求,就可以打开一个SqlSession,返回一个响应后,就关闭它。这个关闭操作很重要,为了确保每次都能执行关闭操作,
你应该把这个关闭操作放到 finally 块中。
maven默认不会将src/main/java中的xml文件编译,在pom.xml中增加配置
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
优化配置
读取配置文件获取数据库连接的参数
在配置文件"mybatis-config.xml"中,数据库连接的参数可以从properties文件中获取
db.properties配置文件
db.url=jdbc:mysql://localhost:3306/mybatis_db?serverTimezone=UTC
db.username=root
db.password=123
db.driverClassName=com.mysql.jdbc.Driver
在mybatis-config.xml文件中增加如下配置来读取配置文件
<!--ref代表读取网络文件-->
<!--resource属性代表读取本地文件,默认从classes目录下读取-->
<properties resource="db.properties"/>
mybatis-config.xml文件中修改数据库连接的参数
<dataSource type="POOLED">
<!--使用${}读取配置文件中的值-->
<property name="driver" value="${db.driverClassName}"/>
<property name="url" value="${db.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
</dataSource>
日志配置
MyBatis框架在执行时是黑箱操作,MyBatis框架记录了日志,只需要增加jar包,编写配置文件就可以展示执行过程中的信息。
导入jar包
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
配置文件log4j.properties
#USE THIS SETTING FOR OUTPUT MYBATIS`s SQL ON THE CONSOLE
log4j.rootLogger=debug, Console,E
#Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
#log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
log4j.appender.Console.layout.ConversionPattern=[ %l ] %d{yyyy/MM/dd HH:mm:ss} %-5p [%c{1}] - %l - %m%n
### \u4FDD\u5B58\u5F02\u5E38\u4FE1\u606F\u5230\u5355\u72EC\u6587\u4EF6 ###
log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File = error.log
log4j.appender.E.Append = true
log4j.appender.E.Threshold = ERROR
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
再运行程序就会输出日志,比如执行的Sql语句,参数以及查询的数据条数
[ org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:159) ] 2020/05/22 16:23:29 DEBUG [selectAll] - org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:159) - ==> Preparing: select * from user
[ org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:159) ] 2020/05/22 16:23:29 DEBUG [selectAll] - org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:159) - ==> Parameters:
[ org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:159) ] 2020/05/22 16:23:29 DEBUG [selectAll] - org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:159) - <== Total: 2
Mapper映射文件
在UserMapper.xml文件中配置增删改查的Sql语句
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.young.mapper.UserMapper">
<!--查询所有数据-->
<select id="selectAll" resultType="com.young.model.User">
select * from user
</select>
<!--根据id查询单条数据-->
<select id="selectById" resultType="com.young.model.User">
select * from user where id=#{id}
</select>
<!--更新数据-->
<update id="update" parameterType="com.young.model.User">
update user set username=#{username} where id=#{id}
</update>
<!--插入数据-->
<insert id="insert" parameterType="com.young.model.User">
insert into user values(#{id},#{username},#{password})
</insert>
<!--删除数据-->
<delete id="delete">
delete from user where id=#{id}
</delete>
</mapper>
测试类
测试根据id查询单条数据
在其他测试代码中省略获取SqlSession实例的过程
@Test
public void testMapper() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sessionFactory.openSession();
User user = session.selectOne("com.young.mapper.UserMapper.selectById",1);
System.out.println(JSON.toJSONString(user));
}
测试更新数据
User user = new User();
user.setId(1);
user.setUsername("abc");
//返回影响的行数
session.update("com.young.mapper.UserMapper.update", user);
//MyBatis默认为手动提交,所以对于增删改操作需要调用commit()方法
session.commit();
测试插入数据
User user = new User();
user.setId(5);
user.setPassword("555");
user.setUsername("eee");
//返回影响的行数
session.insert("com.young.mapper.UserMapper.insert", user);
session.commit();
插入数据时的日志记录
根据日志可以看出,他会根据根据参数的类型和名称实现注入
[ org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:159) ] 2020/05/22 16:31:49 DEBUG [insert] - org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:159) - ==> Preparing: insert into user values(?,?,?)
[ org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:159) ] 2020/05/22 16:31:49 DEBUG [insert] - org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:159) - ==> Parameters: 5(Integer), eee(String), 555(String)
[ org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:159) ] 2020/05/22 16:31:49 DEBUG [insert] - org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:159) - <== Updates: 1
[ org.apache.ibatis.transaction.jdbc.JdbcTransaction.commit(JdbcTransaction.java:70) ] 2020/05/22 16:31:49 DEBUG [JdbcTransaction] - org.apache.ibatis.transaction.jdbc.JdbcTransaction.commit(JdbcTransaction.java:70) - Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@467aecef]
测试删除数据
//返回影响的行数
session.delete("com.young.mapper.UserMapper.delete", 5);
session.commit();
resultType:表示执行Sql语句返回结果的类型
selectList()方法会返回一个List集合,MyBatis会将查询出的每一条数据封装为User对象并存入List集合。
selectOne()方法会返回一个resultType属性指定类的对象,MyBatis会将查询出单条数据封装为User对象。
如果查询出多条数据却使用了selectOne()方法,会出现异常。
如果返回结果为自定义类型,结果的列明需要和自定义类的setXxx()方法中"Xxx"名称保持一致。如setUsername(),那么列明就为"username"
parameterType:表示输入参数的类型
使用#{}作为参数的占位符
如果数据类型为基本数据类型(包括包装类)以及String,可以不用设置输入参数类型,MyBatis会实现自动注入
如果指定输入参数的类型为自定义类,如User类,那么User类中的属性名需要和#{}中的参数名一致
区分${}和#{}
${}在mybatis-config.xml中可以读取配置文件中的内容,也可以用在mapper.xml中,但是慎用!
${}值只可以为value,且只会将值直接拼接在sql语句中,无任何处理,如下所示:
<select id="selectByName" resultType="com.young.model.User">
select * from user where username=${value}
</select>
在运行时,Sql语句为:"select * from user where username=aaa";当值为String类型时,不会给值拼接引号。
而#{}中的值为列名,比如${id}、${username},且将值拼接在sql语句中时会拼接引号
在运行时,Sql语句为:"select * from user where username='aaa'";
所以如果要使用${},需要在传递参数时加上引号,而且使用${}时如果没有拦截器存在发生Sql注入的风险。
在UserMapper.xml文件中,对于resultType以及parameterType每次都要写完整的包名及类名,可以使用别名来简化书写
在mybatis-config.xml文件中增加如下配置
<typeAliases>
<!--只能对单个类进行别名设置-->
<typeAlias type="com.young.model.User" alias="User"/>
<!--如果包中类过多时,可以使用<package>标签对当前包以及子包中的所有类进行别名的设置-->
<!--默认包下的类名就是别名,不区分大小写-->
<!--<package name="com.young.model"/>-->
</typeAliases>
当<typeAlias>标签进行别名设置时,在在UserMapper.xml文件中Sql语句标签如下:
<select id="selectAll" resultType="User">
select * from user
</select>
当<package>标签进行别名设置时,在在UserMapper.xml文件中Sql语句标签如下:
<select id="selectAll" resultType="user">
select * from user
</select>
resultType的值可以为"user",也可以为"User"。
需要注意<configuration>节点中的内容顺序,否则会报错。顺序如下:
properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?"。