本教程旨在向您展示如何使用简单的 Java 项目设置 iBatis (MyBatis),还将展示如何配置高级映射(结果映射)、关联(1 到 1)、集合(1 表示 n,n 到 n)、使用 XML 配置和注释选择 n+1 问题的示例。
先决条件
对于本教程,在 Eclipse 中,项目的结构将如下所示:
示例数据
在启动项目之前,运行 sql 文件夹中的脚本。此文件包含本教程中使用的示例数据。
第 1 名 - POJO - 豆类
我将用 UML 图表示 Bean,以便更好地可视化。完整的源代码可以在本教程结束时下载。
本文的目的是演示如何从数据库中检索所有博客信息,但如您所见,“博客”类包含一个关联(Author)和一个帖子集合(它包含一个标记集合)。我们将尝试一次检索所有这些信息。
因此,我们将使用iBatis/Mybatis演示一对一,一对多和多对多关系。
2 - 高级映射 - 结果映射
上述代码中的结果映射是高级结果映射。正如我在之前的文章中已经提到的,结果映射元素是MyBatis中最重要和最强大的元素。
MyBatis的创建有一个想法:数据库并不总是你想要的或需要的。虽然我们希望每个数据库都是完美的第三范式或BCNF,但事实并非如此。如果有可能将单个数据库完美地映射到所有使用它的应用程序,那就太好了,但事实并非如此。
resultMap 元素有许多子元素和一个值得讨论的结构。以下是结果映射元素的概念视图。
- 构造函数 – 用于在实例化时将结果注入类的构造函数
- id – ID 结果,将结果标记为 ID 将有助于提高整体性能
- 结果 – 注入字段或 JavaBean 属性的正常结果
- 关联 – 复杂类型关联;许多结果将汇总到此类型中
- 集合 – 复杂类型的集合
- 鉴别器 – 使用结果值来确定要使用的结果映射。
最佳实践:始终以增量方式生成结果映射。单元测试在这里确实有帮助。如果你试图同时构建一个巨大的结果地图,就像你上面一样,很可能你会弄错,而且很难使用。从简单的开始,一步一步地发展它。和单元测试!使用框架的缺点是它们有时有点像黑匣子(开源与否)。确保实现预期行为的最佳选择是编写单元测试。在提交错误时使用它们也会有所帮助
我们的目标是实现以下结果映射。
<resultMap id="resultBlog" type="Blog">
<id property="id" column="idBlog" />
<result property="name" column="blogname" />
<result property="url" column="blogurl" />
<association property="author" column="idBlog" javaType="Author"
select="selectAuthor" />
<collection property="posts" column="idBlog" javaType="ArrayList"
ofType="Post" select="selectPosts" resultMap="resultTag" />
</resultMap>
但是,让我们在当时迈出一步。我们将开始仅检索 Blog 数据,因此我们的初始结果映射和查询将如下所示:
<resultMap id="resultBlog" type="Blog">
<id property="id" column="idBlog" />
<result property="name" column="blogname" />
<result property="url" column="blogurl" />
</resultMap>
<select id="selectBlog" resultMap="resultBlog">
SELECT idBlog, name as blogname, url as blogurl FROM BLOG
</select>
到目前为止,一切都很好,我们在之前的帖子中看到了一个简单的选择。让我们继续下一步。
关联
现在,我们还尝试检索作者数据。
关联元素处理“有一”类型关系。例如,在我们的示例中,博客有一个作者。关联映射的工作方式与任何其他结果非常相似。您可以指定目标属性、要从中检索值的列、属性的 javaType(MyBatis 在大多数情况下可以计算出来)、jdbcType(如有必要)以及类型处理程序(如果要覆盖结果值的检索)。
关联的不同之处在于,您需要告诉 MyBatis 如何加载关联。我的巴蒂斯可以通过两种不同的方式做到这一点:
- 嵌套选择:通过执行另一个返回所需复杂类型的映射 SQL 语句。
- 嵌套结果:通过使用嵌套结果映射来处理联接结果的重复子集。
让我们先来看看嵌套选择。
这是我们的结果映射与作者关联。
<resultMap id="resultBlog" type="Blog">
<id property="id" column="idBlog" />
<result property="name" column="blogname" />
<result property="url" column="blogurl" />
<association property="author" column="idBlog" javaType="Author"
select="selectAuthor" />
</resultMap>
<select id="selectBlog" resultMap="resultBlog">
SELECT idBlog, name as blogname, url as blogurl FROM BLOG
</select>
<select id="selectAuthor" parameterType="int" resultType="Author">
SELECT idAuthor as id, name, email FROM AUTHOR WHERE idBlog = #{idBlog}
</select>
查看“选择”=“选择作者”属性。这意味着 MyBatis 将运行查询以获取写入博客的作者。要建立博客和作者之间的关系,我们指定属性列=“idBlog”,以过滤作者。
请注意,我们还指定了 java 类型 =“作者”。我们使用的是别名(还记得吗?如果您没有别名,则必须将类的全名(与包名称放在一起:com.loiane.model.author)。
就是这样。我们有2个带有选择语句的查询:一个用于从博客上传数据,另一个用于加载作者,博客结果Map描述MyBatis应使用“选择作者”语句来获取作者信息。
此方法非常简单,但对于大型数据集或列表效果不佳。此问题称为“选择 N+1 问题”。此问题是由以下原因引起的:
- 运行返回记录列表 (“+1”) 的 SQL。
- 对于返回的每条记录,运行加载详细信息的 SELECT(“N”)。
此问题可能导致数百或数千个 SELECT 运行。这有时不是你想要的。好的部分是iBatis可以执行这样的查询,但您应该意识到这在性能方面是昂贵的,即性能不佳。
我将在文章中向您展示如何避免此问题,但首先我们将了解如何处理列表或集合。
收集
我们已经在数据库中查找来自博客和作者的信息。博客包含帖子列表,而这些帖子又包含标签列表。我们现在正在处理2个关系:1-N(博客文章)和N-N(帖子标签)。让我们看看如何使用我的巴蒂斯来做到这一点。
让我们仍然使用嵌套选择来执行此操作。
看看带有 Post 集合的结果映射:
<resultMap id="resultBlog" type="Blog">
<id property="id" column="idBlog" />
<result property="name" column="blogname" />
<result property="url" column="blogurl" />
<association property="author" column="idBlog" javaType="Author"
select="selectAuthor" />
<collection property="posts" column="idBlog" javaType="ArrayList"
ofType="Post" select="selectPosts" resultMap="resultTag" />
</resultMap>
集合元素的工作方式与关联的工作方式几乎相同。让我们关注差异。
首先,我们使用集合元素。然后你会注意到有“的类型”属性。需要此特性来注意 JavaBean 属性类型(特性)与集合所包含的类型之间的差异。
为了处理 Post 和 Tag 之间的 N-N 关系,我们还将使用集合元素,但我们不会为此使用嵌套结果。
<resultMap id="resultPosts" type="Post">
<id property="id" column="idPost" />
<result property="title" column="title" />
<collection property="tags" column="idPost" javaType="ArrayList"
ofType="Tag" resultMap="resultTag" />
</resultMap>
<resultMap id="resultTag" type="Tag">
<id property="id" column="idTag" />
<result property="value" column="value" />
</resultMap>
<select id="selectPosts" parameterType="int" resultType="Post"
resultMap="resultPosts">
SELECT
P.idPost as idPost, P.title as title,
T.idTag as idTag, T.value as value
FROM Post P
left outer join Post_Tag PT on P.idPost = PT.idPost
left outer join Tag T on PT.idTag = T.idTag
WHERE P.idBlog = #{idBlog}
</select>
准备!让我们看看Blog.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="Blog">
<resultMap id="resultBlog" type="Blog">
<id property="id" column="idBlog"/>
<result property="name" column="blogname"/>
<result property="url" column="blogurl"/>
<association property="author" column="idBlog" javaType="Author" select="selectAuthor"/>
<collection property="posts" column="idBlog" javaType="ArrayList" ofType="Post"
select="selectPosts" resultMap="resultTag"/>
</resultMap>
<resultMap id="resultPosts" type="Post">
<id property="id" column="idPost"/>
<result property="title" column="title"/>
<collection property="tags" column="idPost" javaType="ArrayList" ofType="Tag"
resultMap="resultTag"/>
</resultMap>
<resultMap id="resultTag" type="Tag">
<id property="id" column="idTag"/>
<result property="value" column="value"/>
</resultMap>
<select id="selectBlog" resultMap="resultBlog">
SELECT idBlog, name as blogname, url as blogurl FROM BLOG
</select>
<select id="selectAuthor" parameterType="int" resultType="Author">
SELECT idAuthor as id, name, email FROM AUTHOR WHERE idBlog = #{idBlog}
</select>
<select id="selectPosts" parameterType="int" resultType="Post" resultMap="resultPosts">
SELECT
P.idPost as idPost, P.title as title,
T.idTag as idTag, T.value as value
FROM Post P
left outer join Post_Tag PT on P.idPost = PT.idPost
left outer join Tag T on PT.idTag = T.idTag
WHERE P.idBlog = #{idBlog}
</select>
</mapper>
N+1 选择问题的解决方案
如何解决这个问题?
使用嵌套结果: - 使用嵌套映射来处理连接结果中的重复子集。
我们需要做的是编写一个查询来一次获取所有信息,ResultMap 处理结果。
非常重要:ID 元素在嵌套结果映射中起着非常重要的作用。 应始终指定一个或多个可用于将结果标识为唯一的属性。事实是,如果您不指定,MyBatis仍然可以工作,但它会降低性能。在这种情况下,主键是一个明显的选择(即使它是复合键)。
<?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="BlogBestPractice">
<resultMap id="resultBlog" type="Blog">
<id property="id" column="idBlog"/>
<result property="name" column="blogName"/>
<result property="url" column="url"/>
<association property="author" column="idBlog" javaType="Author">
<id property="id" column="idAuthor"/>
<result property="name" column="authorName"/>
<result property="email" column="email"/>
</association>
<collection property="posts" column="idBlog" javaType="ArrayList" ofType="Post">
<id property="id" column="idPost"/>
<result property="title" column="title"/>
<collection property="tags" column="idBlog" javaType="ArrayList" ofType="Tag">
<id property="id" column="idTag"/>
<result property="value" column="value"/>
</collection>
</collection>
</resultMap>
<select id="selectBlogBestPractice" resultMap="resultBlog">
SELECT
B.idBlog as idBlog, B.name as blogName, B.url as url,
A.idAuthor as idAuthor, A.name as authorName, A.email as email ,
P.idPost as idPost, P.title as title,
T.idTag as idTag, T.value as value
FROM BLOG as B
left outer join Author A on B.idBlog = A.idBlog
left outer join Post P on P.idBlog = B.idBlog
left outer join Post_Tag PT on P.idPost = PT.idPost
left outer join Tag T on PT.idTag = T.idTag
</select>
</mapper>
3 - 博客道
package com.loiane.dao;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import com.loiane.model.Blog;
/**
* Blog DAO - xml configuration
*
* @author Loiane Groner http://loianegroner.com (English) http://loiane.com
* (Portuguese)
*/
public class BlogDAO {
/**
* Returns the list of all Contact instances from the database.
*
* @return the list of all Contact instances from the database.
*/
@SuppressWarnings("unchecked")
public List<Blog> select() {
SqlSessionFactory sqlSessionFactory = MyBatisConnectionFactory.getSqlSessionFactory();
SqlSession session = sqlSessionFactory.openSession();
try {
List<Blog> list = session.selectList("Blog.selectBlog");
return list;
} finally {
session.close();
}
}
/**
* Returns the list of all Contact instances from the database avoiding the
* N + 1 problem
*
* @return the list of all Contact instances from the database.
*/
@SuppressWarnings("unchecked")
public List<Blog> selectN1ProblemSolution() {
SqlSessionFactory sqlSessionFactory = MyBatisConnectionFactory.getSqlSessionFactory();
SqlSession session = sqlSessionFactory.openSession();
try {
List<Blog> list = session.selectList("BlogBestPractice.selectBlogBestPractice");
return list;
} finally {
session.close();
}
}
}
4 - 注释
package com.loiane.data;
import java.util.List;
import org.apache.ibatis.annotations.Many;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import com.loiane.model.Author;
import com.loiane.model.Blog;
import com.loiane.model.Post;
/**
* Blog Mapper contains all the myBatis/iBatis annotations
*
* @author Loiane Groner http://loianegroner.com (English) http://loiane.com
* (Portuguese)
*/
public interface BlogMapper {
final String SELECT_POSTS = "SELECT P.idPost as idPost, P.title as title, T.idTag as idTag, T.value as value "
+ "FROM Post P left outer join Post_Tag PT on P.idPost = PT.idPost "
+ "left outer join Tag T on PT.idTag = T.idTag WHERE P.idBlog = #{idBlog}";
/**
* Returns the list of all Blog instances from the database.
*
* @return the list of all Blog instances from the database.
*/
@Select("SELECT idBlog, name as blogname, url as blogurl FROM BLOG")
@Results(value = {
@Result(property = "id", column = "idBlog"),
@Result(property = "name", column = "blogname"),
@Result(property = "url", column = "blogurl"),
@Result(property = "author", column = "idBlog", javaType = Author.class, one = @One(select = "selectAuthor")),
@Result(property = "posts", column = "idBlog", javaType = List.class, many = @Many(select = "selectBlogPosts"))
})
List<Blog> selectAllBlogs();
/**
* Returns the list of all Author instances from the database of a Blog
*
* @param idBlog
* @return the list of all Author instances from the database of a Blog
*/
@Select("SELECT idAuthor as id, name, email FROM AUTHOR WHERE idBlog = #{idBlog}")
Author selectAuthor(String idBlog);
/**
* Returns the list of all Post instances from the database of a Blog
*
* @param idBlog
* @return the list of all Post instances from the database of a Blog
*/
@Select(SELECT_POSTS)
@Results(value = {
@Result(property = "id", column = "idPost"),
@Result(property = "title", column = "title"),
@Result(property = "tags", column = "idPost", javaType = List.class, many = @Many)
})
List<Post> selectBlogPosts(String idBlog);
}
@结果
列和属性之间的单个映射。特性:标识、列、属性、javaType、jdbcType、类型处理程序、 一个、 多个。id 属性是一个布尔值,指示是否应将该属性用于比较。(类似于 XML 配置中的<id> 标记)。一个属性用于关联,类似于<关联> 标记, 许多属性用于收集,类似于<集合>。他们以这种方式命名以避免冲突。
@One
复杂类型的单个属性之间的映射。属性:选择为获取结果而必须执行的查询的名称(映射器方法名称)。
@Many
作为复杂类型集合的属性之间的映射。属性:选择为获取结果而必须执行的查询的名称(映射器方法名称)。
5 - SqlMapConfig.xml
文件.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>
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
<typeAliases>
<typeAlias alias="Blog" type="com.loiane.model.Blog"/>
<typeAlias alias="Author" type="com.loiane.model.Author"/>
<typeAlias alias="Post" type="com.loiane.model.Post"/>
<typeAlias alias="Tag" type="com.loiane.model.Tag"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/blog_ibatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/loiane/data/Blog.xml"/>
<mapper resource="com/loiane/data/BlogBestPractice.xml"/>
</mappers>
</configuration>
6 - 我的巴蒂斯连接工厂
package com.loiane.dao;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import com.loiane.data.BlogMapper;
/**
* MyBatis Connection Factory, which reads the configuration data from a XML
* file.
*
* @author Loiane Groner http://loianegroner.com (English) http://loiane.com
* (Portuguese)
*/
public class MyBatisConnectionFactory {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
String resource = "SqlMapConfig.xml";
Reader reader = Resources.getResourceAsReader(resource);
if (sqlSessionFactory == null) {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
sqlSessionFactory.getConfiguration().addMapper(BlogMapper.class);
}
} catch (FileNotFoundException fileNotFoundException) {
fileNotFoundException.printStackTrace();
} catch (IOException iOException) {
iOException.printStackTrace();
}
}
public static SqlSessionFactory getSqlSessionFactory() {
return sqlSessionFactory;
}
}
Main
package com.loiane.data;
import com.loiane.model.Blog;
import java.io.IOException;
import java.io.Reader;
import java.util.List;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class MyBatisDemo {
private static SqlSessionFactory factory = null;
public static void main(String[] args) throws IOException {
String resource = "SqlMapConfig.xml";
Reader reader = null;
SqlSession session = null;
reader = Resources.getResourceAsReader(resource);
factory = new SqlSessionFactoryBuilder().build(reader);
//factory.getConfiguration().addMapper(MyMapper.class);
// factory.getConfiguration().addMapper(BlogMapper.class);
reader.close();
try {
session = factory.openSession();
// String version = session.selectOne("getMySQLVersion");
// System.out.println(version);
List<Blog> list;
list = session.selectList("Blog.selectBlog");
for (Blog blog : list) {
System.out.println(blog.toString());
}
list = session.selectList("BlogBestPractice.selectBlogBestPractice");
for (Blog blog : list) {
System.out.println(blog.toString());
}
} finally {
if (session != null) {
session.close();
}
}
}
}
下载
https://github.com/allwaysoft/MyBatisMySQLDemoOne2OneAndOne2Many