一、Mybatis理解:
MyBatis 是一个开源、轻量级的数据持久化框架,是 JDBC 和 Hibernate 的替代方案。MyBatis 内
部封装了 JDBC,简化了加载驱动、创建连接、创建 statement 等繁杂的过程,开发者只需要关注
SQL 语句本身。不用关心底层数据库的连接,结果集的遍历等。
MyBatis 的主要思想是将程序中的大量 SQL 语句剥离出来,使用 XML 文件或注解的方式实现
SQL 的灵活配置,将 SQL 语句与程序代码分离,在不修改程序代码的情况下,直接在配置文件中
修改 SQL 语句。
二、MyBatis快速开始
第一步:准备工作
1.下载并添加MyBatis依赖
官网地址:https://github.com/mybatis/mybatis-3/releases
2.创建数据库表结构(以website为例)
3.创建实体类
在 src 目录下创建一个名为com.apesource.entity 的包,在该包中创建实体类 Website。
实体类名与表明一致,成员变量名与表中字段名一致,数据类型也要与数据库一致。
public class Website {
private int id;
private String name;
private String url;
private int age;
private int alexa;
private int uv;
private String country;
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getAlexa() {
return alexa;
}
public void setAlexa(int alexa) {
this.alexa = alexa;
}
public int getUv() {
return uv;
}
public void setUv(int uv) {
this.uv = uv;
}
@Override
public String toString() {
return "Website [id=" + id + ", name=" + name + ", url=" + url + ", age=" + age + ", alexa=" + alexa + ", uv="
+ uv + ", country=" + country + "]";
}
}
第二步:SQL映射文件
在 src 目录下创建 com.apesource.mapper 包,在该包下创建映射文件 WebsiteMapper.xml。
<?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.apesource.mapper.WebsiteMapper">
//parameterType:输入类型
<!-- 添加一个网站 -->
<insert id="insertWebsite" parameterType="com.apesource.entity.Website">
insert into website
(name,url,age,country)
values(#{name},#{url},#{age},#{country})
</insert>
// resultType属性:将输出结果封装成指定类型
<!-- 查询所有网站信息 -->
<select id="selectAllWebsite"
resultType="com.apesource.entity.Website">
select * from website
</select>
</mapper>
<mapper>是配置文件的根元素,包含namespace(命名空间)属性,该属性值通常设置为“包名+SQL映射文件名”,用于指定唯一的命名空间。
查询语句写在<select> 中,添加语句写在<insert>中。
所有的子元素都要写在<mapper>节点中,SQL语句中,“#{}”表示一个占位符,相当于“?”,
而“#{name}”表示该占位符用于接收参数中的名称为 name的参数值。
第三步:核心配置文件
MyBatis 核心配置文件主要用于配置数据库连接和 MyBatis运行时所需的各种特性,包含了设置和影响 MyBatis 行为的属性。
在 src 目录下创建 MyBatis的核心配置文件 mybatis-config.xml,在该文件中配置了数据库环境和
映射文件的位置,具体内容如下:
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!-- 日志 -->
<setting name="logImpl" value="LOG4J" />
</settings>
<!-- 配置mybatis运行环境 -->
<environments default="development">
<environment id="development">
<!-- 使用JDBC的事务管理 -->
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<!-- MySQL数据库驱动 -->
<property name="driver" value="com.mysql.cj.jdbc.Driver" />
<!-- 连接数据库的URL -->
<property name="url"
value="jdbc:mysql://localhost:3306/test_db?charset=utf8mb4&useSSL=false&useTimezone=true&serverTimezone=GMT%2B8" />
<property name="username" value="root" />
<property name="password" value="123456" />
</dataSource>
</environment>
</environments>
<!-- 将mapper文件加入到配置文件中 -->
<mappers>
<mapper resource="com/mapper/WebsiteMapper.xml" />
</mappers>
</configuration>
第四步:日志文件
MyBatis 默认使用 log4j 输出日志信息,如果开发者需要查看控制台输出的 SQL 语句,可以在 classpath 路径下配置其日志文件。在 mybatisDemo 的 src 目录下创建 log4j.properties 文件,其内容如下:
# Global logging configuration
log4j.rootLogger=ERROR,stdout
# MyBatis logging configuration...
log4j.logger.com.apesource=DEBUG
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
第五步:测试类
public class Test {
public static void main(String[] args) throws IOException {
//SqlSessionFactoryBuilder建造者
SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();
//通过SqlSessionFactoryBuilder建造者,创建SqlSessionFactory工厂
InputStream in=Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sessionFactory=sqlSessionFactoryBuilder.build(in);
//通过SqlSession查找SQL映射,并执行
//SQL映射的查找:通过命名空间+SQL映射ID
SqlSession sqlSession=sessionFactory.openSession();
List<Website> websitelist= sqlSession.selectList("com.mapper.WebsiteMapper.selectAllWebsite");
for(Website website :websitelist){
System.out.println(website);
}
}
}
三、MyBatis核心对象
MyBatis 的核心接口和类包括:SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession。
SqlSessionFactoryBuilder对象,可以根据配置文件或者onfiguration 类的实例构建该对象。
通过 SqlSessionFactoryBuilder对象来获取 SqlSessionFactory 对象。获取SqlSessionFactory 对
象后,调用openSession(),获取SqlSession 实例。SqlSession 对象中完全包含以数据库为背景的
所有执行 SQL 操作的方法,用该实例可以直接执行已映射的 SQL 语句。
核心对象的生命周期和作用域
SqlSessionFactoryBuilder
这个类可以被实例化、使用和丢弃,一旦创建了SqlSessionFactory,就不再需要它了。 因此
SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 可以重用
SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例。
SqlSessionFactory
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创
建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次
重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是"应用
作用域"。即随着应用程序的生命周期一直存在。这种“存在于整个应用运行期间,并且只存在一个
对象实例”。例如使用单例模式。
SqlSession
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被
共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一
个类的静态域,甚至一个类的实例变量也不行。为了确保每次都能执行关闭操作,应该把这个关
闭操作放到 finally 块中。或者放入try()中,保证每次都能关闭。
MyBatis工具类
public class MyBatisUtils {
private static SqlSessionFactory factory=null;
static {
String config="mybatis-config.xml";
try {
InputStream in=Resources.getResourceAsStream(config);
factory=new SqlSessionFactoryBuilder().build(in);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static SqlSession getSqlSession() {
SqlSession sqlSession=null;
if(factory !=null) {
sqlSession=factory.openSession();
}
return sqlSession;
}
}
四、Mapper映射器
Mapper映射器是 MyBatis 中最重要的文件,文件中包含一组 SQL 语句(例如查询、添加、删除、修改),这些语句称为映射语句或 SQL映射语句。
映射器由Java接口和XML文件共同组成。
映射器接口:
映射器接口名称要与XML文件名称一致。接口的中的方法名称必须与SQL映射操作的
ID一致,并且ID唯一。映射接口中方法不允许重载,因为每个方法名都将被作为一个Mapped
Statement的一个ID。
XML文件:
在xml文件中,使用<select><insert><delete><update>等节点进行SQL语句的映射配置。这些节点都要写在<mapper> </mapper>中。<mapper>映射文件的根节点,只有 namescape 一个属性,namespace用于区分不同的 mapper,当 namescape 绑定某一接口后,可以不用写该接口的实现类,MyBatis 会通过接口的完整限定名查找到对应的 mapper 配置来执行 SQL 语句。因此 namescape 的命名必须要跟接口同名。
<select id="selectPerson" parameterType="int" resultType="hashmap">
SELECT * FROM PERSON WHERE ID = #{id}
</select>
<insert id="insertAuthor">
insert into Author (id,username,password,email,bio)
values (#{id},#{username},#{password},#{email},#{bio})
</insert>
<update id="updateAuthor">
update Author set
username = #{username},
password = #{password},
email = #{email},
bio = #{bio}
where id = #{id}
</update>
<delete id="deleteAuthor">
delete from Author where id = #{id}
</delete>
传递多个参数的三种方式:
在进行模糊查询,或按区间查询时需要传递多个参数,有三种方法。
1.使用Map传递参数
WebsiteMapper 接口:
public List<Website> selectWebsiteByMap(Map<String, String> params);
在WebsiteMapper.xml中定义<select>节点:
<!-- 根据name和url模糊查询网站信息 -->
<select id="selectWebsiteByMap"
resultType="com.apesource.entity.Website"
parameterType="map">
SELECT id,NAME,url FROM website
WHERE name LIKE CONCAT ('%',#{name},'%')
AND url LIKE CONCAT ('%',#{url},'%')
</select>
测试代码:
Map<String,String> paramsMap = new HashMap<String,String>();
paramsMap.put("name","网易");
paramsMap.put("url","apesource");
websiteMapper.selectWebsiteByMap(paramsMap);
2.使用注解传递参数
在WebsiteMapper.xml中定义<select>节点:
<!-- 根据name和url模糊查询网站信息 -->
<select id="selectWebsiteByMap"
resultType="com.apesource.entity.Website"
parameterType="map">
SELECT id,NAME,url FROM website
WHERE name LIKE CONCAT ('%',#{name},'%')
AND url LIKE CONCAT ('%',#{url},'%')
</select>
WebsiteMapper接口:MyBatis 通过 @Param 提供的名称就会知道 #{name} 代表 name 参数,提高了参数可读性
public List<Website> selectWebsiteByAn(@Param("name") String name, @Param("url") String url);
3. 使用JavaBean传递参数
WebsiteMapper接口:
public List<Website> selectWebsiteByAn(Website website);
主键回填(主键为自增)
添加数据时,需要拿到该数据id便于后续操作。
通过在<insert>标签里设置:
useGeneratedKeys属性:设置为true。
keyProperty属性:实体类的"id"成员变量。
在测试类中通过成员变量的get()拿到"自增主键"。
resultMap 元素
resultMap主要用于解决实体类属性名与数据库表中字段名不一致的情况,可以将查询结果映射成实体对象。
<!-- 根据orderid查询订单信息 -->
<!-- 单步查询 -->
<resultMap type="com.entity.Order" id="orderresultMap">
<!-- 属性 <==>字段(查询结果虚拟表) -->
<id property="orderid" column="orderid"/>
<result property="ordername" column="ordername"/>
<!-- 1个订单属于1个用户 -->
<association property="user"
javaType="com.entity.User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="pwd" column="pwd"/>
</association>
<!-- 1个订单中包含多个商品 -->
<collection property="products"
ofType="com.entity.Product">
<id column="pid" property= "pid" />
<result column="name" property= "name"/>
<result column="price" property= "price"/>
</collection>
</resultMap>
<select id="selectOrderByOrderId"
resultMap="orderresultMap">
SELECT oi.* ,u.*,
pi2.name as pname,
pi2.price as price,
pi2.pid as pid
FROM order_info oi
INNER JOIN user u ON oi.userId=u.id
INNER JOIN order_detail od on oi.orderid=od.order_id
INNER JOIN product_info pi2 on od.product_id=pi2.pid
where oi.orderid=#{id}
</select>