MyBatis 的前身是 iBatis,iBatis 是 Apache 软件基金会下的一个开源项目,2010年该项目从 Apache 基金会迁出,并改名为 MyBatis。
MyBatis 是一种半自动化的 Java 持久层框架,它通过注解或 XML 的方式将对象和SQL关联起来。之所以是半自动的,是与 Hibernate 等一些可自动生成 SQL 的 ORM 框架相比,MyBatis 需要用户自己维护SQL。虽然维护SQL的工作比较繁琐,但也有好处,比如我们可以控制 SQL 的执行逻辑,对语句进行性能优化,而不用像 ORM 框架那样对底层执行 sql 不能自由控制。
为什么使用 MyBatis,最好的解释就是比较一下不同的数据库访问方式的差异,大家就能发现各种方式的优缺点,从而能根据不同场景选择不同的访问方式。
在 mysql 中创建名为 mybatis_demo 的数据库,并创建一个名为 article 的表:
- CREATE DATABASE IF NOT EXISTS mybatis_demo DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci;
- CREATE TABLE IF NOT EXISTS mybatis_demo.`article` (
- `id` int(11) NOT NULL AUTO_INCREMENT,
- `title` varchar(100) DEFAULT NULL,
- `author` varchar(100) DEFAULT NULL,
- `content` text,
- `create_time` datetime DEFAULT NULL,
- PRIMARY KEY (`id`)
- ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
-
- -- 插入数据
- INSERT INTO mybatis_demo.article
- (id, title, author, content, create_time)
- VALUES(1, 'title1', 'phenix', 'demo...', '2022-08-15 01:02:00.000');
-
创建 maven 项目,pom 依赖如下:
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
-
- <groupId>com.phenix</groupId>
- <artifactId>MybatisLearn</artifactId>
- <version>1.0-SNAPSHOT</version>
-
- <properties>
- <maven.compiler.source>8</maven.compiler.source>
- <maven.compiler.target>8</maven.compiler.target>
- <spring.version>5.3.20</spring.version>
- </properties>
-
- <dependencies>
-
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <version>RELEASE</version>
- <scope>compile</scope>
- </dependency>
-
- <dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-lang3</artifactId>
- <version>3.12.0</version>
- </dependency>
-
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <version>8.0.27</version>
- </dependency>
-
- <dependency>
- <groupId>org.mybatis</groupId>
- <artifactId>mybatis</artifactId>
- <version>3.5.6</version>
- </dependency>
-
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>RELEASE</version>
- <scope>test</scope>
- </dependency>
-
- <!-- spring jdbc start -->
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-core</artifactId>
- <version>${spring.version}</version>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-jdbc</artifactId>
- <version>${spring.version}</version>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-tx</artifactId>
- <version>${spring.version}</version>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-test</artifactId>
- <version>${spring.version}</version>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-context</artifactId>
- <version>${spring.version}</version>
- </dependency>
- <!-- spring jdbc end -->
-
- <!-- 添加hibernate的依赖 start -->
- <!-- 添加Hibernate依赖,需要使用5.2及以上版本,否则不支持LocalDateTime -->
- <dependency>
- <groupId>org.hibernate</groupId>
- <artifactId>hibernate-core</artifactId>
- <version>5.5.4.Final</version>
- </dependency>
- <!-- 添加C3P0依赖,用于手动配置Hibernate的连接池-->
- <!-- <dependency>-->
- <!-- <groupId>org.hibernate</groupId>-->
- <!-- <artifactId>hibernate-c3p0</artifactId>-->
- <!-- <version>5.2.16.Final</version>-->
- <!-- </dependency>-->
- <!-- 5.1及以下版本支持LocalDateTime的依赖 -->
- <!-- <dependency>-->
- <!-- <groupId>org.hibernate</groupId>-->
- <!-- <artifactId>hibernate-java8</artifactId>-->
- <!-- <version>5.1.2.Final</version>-->
- <!-- </dependency>-->
- <!-- 添加hibernate的依赖 end -->
- </dependencies>
- </project>
-
java 代码中,为 article 创建对象 Article:
- @Data
- @NoArgsConstructor
- @AllArgsConstructor
- @Builder
- @ToString
- public class Article {
- private Integer id;
- private String title;
- private String author;
- private String content;
- private LocalDateTime createTime;
- }
-
创建 mybatis-access-1 目录,新增 mybaits-config.xml,该文件配置了数据库、SQL 脚本相关信息:
- <?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>
- <!-- 数据库连接信息配置在jdbc.properties -->
- <properties resource="jdbc.properties"/>
- <typeAliases>
- <typeAlias alias="Article"
- type="com.phenix.learn.mybatis.model.Article"/>
- <!-- <typeAlias alias="Author" type="xyz.coolblog.model.AuthorDO"/>-->
- </typeAliases>
- <environments default="development">
- <environment id="development">
- <transactionManager type="JDBC"/>
- <dataSource type="POOLED">
- <property name="driver" value="${jdbc.driver}"/>
- <property name="url" value="${jdbc.url}"/>
- <property name="username" value="${jdbc.username}"/>
- <property name="password" value="${jdbc.password}"/>
- </dataSource>
- </environment>
- </environments>
- <mappers>
- <!-- sql文件扫描路径 -->
- <mapper resource="mybatis-access-1/ArticleMapper.xml"/>
- </mappers>
- </configuration>
-
jdbc.properties 配置了数据库连接信息
- jdbc.driver=com.mysql.cj.jdbc.Driver
- jdbc.url=jdbc:mysql://localhost:3306/mybatis_demo?useUnicode=true
- jdbc.username=root
- jdbc.password=******
-
java 代码加入一个 ArticleDao 接口
- public interface ArticleDao {
- List<Article> findByAuthorAndCreateTime(@Param("author") String author,
- @Param("createTime") String createTime);
- }
-
创建 ArticleDao 接口对应的 ArticleMapper.xml,放在 mybatis-access-1 目录下
- <?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.phenix.learn.mybatis.dao.ArticleDao">
- <select id="findByAuthorAndCreateTime" resultType="com.phenix.learn.mybatis.model.Article">
- SELECT
- `id`, `title`, `author`, `content`, `create_time` as createTime
- FROM
- `article`
- WHERE
- `author` = #{author} AND `create_time` > #{createTime}
- </select>
- </mapper>
-
到此,mybatis 相关的配置和 sql 语句都已经写完,下面就来访问数据库
- public class MybatisTest {
- private SqlSessionFactory sqlSessionFactory;
-
- @Before
- public void before() throws IOException {
- String resource = "mybatis-access-1/mybatis-config.xml";
- InputStream inputStream = Resources.getResourceAsStream(resource);
- sqlSessionFactory = new
- SqlSessionFactoryBuilder().build(inputStream);
- inputStream.close();
- }
-
- @Test
- public void testMyBatisAccessDb(){
- SqlSession session = sqlSessionFactory.openSession();
- try {
- ArticleDao articleDao = session.getMapper(ArticleDao.class);
- List<Article> articles = articleDao.
- findByAuthorAndCreateTime("phenix", "2022-08-10");
- System.out.println(articles.toString());
- } finally {
- session.commit();
- session.close();
- }
- }
- }
-
jdbc 访问数据库,首先建立数据库连接,然后执行 sql 语句,再获取 sql 语句处理结果
- public class JDBCTest {
- @Test
- public void testJdbcAccessDb() {
- String connUrl = "jdbc:mysql://localhost:3306/mybatis_demo?useUnicode=true&characterEncoding=utf8&useSSL=false&user=root&password=******";
- try {
- //1、导入驱动jar包
- //2、注册驱动
- Class.forName("com.mysql.cj.jdbc.Driver");
- Connection conn = DriverManager.getConnection(connUrl);
- String author = "phenix";
- String date = "2022-08-10";
- String sql = String.format(Locale.ENGLISH, "SELECT" +
- " `id`, `title`, `author`, `content`, `create_time` FROM `article` WHERE" +
- " `author` = '%s' AND `create_time` > '%s'", author, date);
- Statement stm = conn.createStatement();
- ResultSet resultSet = stm.executeQuery(sql);
- List<Article> articles = new ArrayList<>(resultSet.getRow());
- while (resultSet.next()) {
- Timestamp createTimestamp = resultSet.getTimestamp("create_time");
- Article article = Article.builder().id(resultSet.getInt("id"))
- .author(resultSet.getString("author"))
- .content(resultSet.getString("content"))
- .title(resultSet.getString("title"))
- // ZoneId.of("UTC+8")
- .createTime(createTimestamp != null ? LocalDateTime.ofInstant(createTimestamp.toInstant(), ZoneId.systemDefault()) : null)
- .build();
- articles.add(article);
- }
- articles.forEach(System.out::println);
- } catch (SQLException | ClassNotFoundException ex) {
- ex.printStackTrace();
- }
- }
-
-
- @Test
- public void testJdbcByPreparedStatement() {
- String connUrl = "jdbc:mysql://localhost:3306/mybatis_demo?useUnicode=true&characterEncoding=utf8&useSSL=false&user=root&password=******";
- try {
- //1、导入驱动jar包
- //2、注册驱动
- Class.forName("com.mysql.cj.jdbc.Driver");
- Connection conn = DriverManager.getConnection(connUrl);
- String sql = "SELECT `id`, `title`, `author`, `content`, `create_time` FROM `article`" +
- " WHERE `author` = ? AND `create_time` > ?";
- PreparedStatement ps = conn.prepareStatement(sql);
- ps.setString(1, "phenix");
- ps.setString(2, "2022-08-10");
- ResultSet resultSet = ps.executeQuery();
- List<Article> articles = new ArrayList<>(resultSet.getRow());
- while (resultSet.next()) {
- Timestamp createTimestamp = resultSet.getTimestamp("create_time");
- Article article = Article.builder().id(resultSet.getInt("id"))
- .author(resultSet.getString("author"))
- .content(resultSet.getString("content"))
- .title(resultSet.getString("title"))
- // ZoneId.of("UTC+8")
- .createTime(createTimestamp != null ? LocalDateTime.ofInstant(createTimestamp.toInstant(), ZoneId.systemDefault()) : null)
- .build();
- articles.add(article);
- }
- articles.forEach(System.out::println);
- } catch (SQLException | ClassNotFoundException ex) {
- ex.printStackTrace();
- }
- }
- }
-
从上面的代码可以看出 JDBC 访问数据库的几个问题:
JDBC的使用问题确实不少,但如果项目比较小,还是可以使用JDBC的,没必要如MyBatis一般搞一堆配置。
MyBatis VS JDBC
与 JDBC 相比, MyBatis 缺点比较明显,它的配置比较多,特别是 SQL 映射文件。如果一个大型项目中有几十上百个 Dao 接口,就需要有同等数量的 SQL 映射文件,这些映射文件需要用户自行维护。不过与 JDBC 相比,维护映射文件不是什么大问题。不然如果把同等数量的 SQL 像 JDBC 那样写在代码中,那维护的代价才叫大, 出错的可能性也会增大。除了配置文件的问题,大家会发现使用 MyBatis 访问数据库好像过程也很繁琐啊。它的步骤大致如下:
如果每次执行一个 SQL 要简历如上几步步骤,那和 JDBC 比较起来,也就没什优势了。好在,事实并非如此。 SqlSessionFactoryBuilder 和 SqlSessionFactory 以及 SqlSession 等对象的作用域和生命周期是不一样的,这一点在 MyBatis 官方文档中有所说明。SqlSessionFactoryBuilder 对 象 用 于 构 建 SqlSessionFactory , 只要构建好 ,SqlSessionFactoryBuilder 对象就可以丢弃了。
SqlSessionFactory 是一个工厂类,一旦被创建就应该在应用运行期间一直存在,不应该丢弃或重建。 SqlSession 不是线程安全的,所以不应被多线程共享。官方推荐的使用方式是有按需创建,用完即销毁。因此,以上步骤中,第1、 2和第 3 步只需执行一次。第 4 和第 5 步需要进行多次创建。至于第 6 步,这一步是必须的。 相比之下, MyBatis 访问数据库的步骤要比 JDBC 简练不少。 同时,使用 MyBatis 无需处理受检异常,比如 SQLException。
另外,把 SQL 写在配置文件中,进行集中管理,利于维护。同时将 SQL 从代码中剥离,在提高代码的可读性的同时,也避免了拼接 SQL 可能会导致的错误。除了上面所说这些, MyBatis 会将查询结果映射为相应的对象,无需用户自行处理 ResultSet。
总的来说, MyBatis 在易用性上要比 JDBC 好太多。不过这里拿 MyBatis 和 JDBC 进行对比并不太合适。JDBC作为 Java 平台的数据库访问规范,它仅提供一种访问数据库的能力。至于使用者觉得 JDBC 流程繁琐,还要自行处理异常等问题,这些还真不怪 JDBC。比如 SQLException 这个异常, JDBC 没法处理,也不应该处理,抛给调用者处理也是理所应当。至于繁杂的步骤,这仅是从使用者的角度考虑的,从 JDBC 的角度来说,这里的每个步骤对于完成一个数据访问请求来说都是必须的。至于 MyBatis,它是构建在 JDBC 技术之上的,对访问数据库的操作进行了简化,方便用户使用。综上所述, JDBC 可看做是一种基础服务,MyBatis 则是构建在基础服务之上的框架,它们的目标是不同的。
SpringJDBC 是对 JDBC 一层比较薄的封装,提升了不少易用性。
resource 目录的下新增 spring-jdbc-access/spring-jdbc-config.xml,内容如下:
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:context="http://www.springframework.org/schema/context"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context-4.2.xsd"
- default-lazy-init="true">
- <beans>
- <!--
- Spring容器采用反射扫描的发现机制,在探测到Spring容器中有一个org.springframework.beans.config.PropertyPlaceholderConfigurer的Bean
- 就会停止对剩余PropertyPlaceholderConfigurer的扫描,换句话说,即Spring容器仅允许最多定义一个PropertyPlaceholderConfigurer或
- <content:property-placeholder>,其余的会被Spring忽略;
- 由于Spring容器只能有一个PropertyPlaceholderConfigurer,如果有多个属性文件,这时就看谁先谁后了,先的保留 ,后的忽略。
- 参考文档:https://www.cdsy.xyz/computer/programme/spring/250312/cd73850.html
- -->
- <!-- 路径简单写法可以是:location="classpath*:**/jdbc.properties" -->
- <context:property-placeholder location="classpath:chapter01/jdbc.properties"/>
- <bean id="dataSource"
- class="org.springframework.jdbc.datasource.DriverManagerDataSource">
- <property name="driverClassName" value="${jdbc.driver}"/>
- <property name="url" value="${jdbc.url}"/>
- <property name="username" value="${jdbc.username}"/>
- <property name="password" value="${jdbc.password}"/>
- </bean>
- <bean id="jdbcTemplate"
- class="org.springframework.jdbc.core.JdbcTemplate">
- <property name="dataSource" ref="dataSource"/>
- </bean>
- </beans>
- </beans>
通过 JdbcTemplate 访问数据库
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration("classpath:spring-jdbc-access/spring-jdbc-config.xml")
- public class SpringJdbcTest {
- @Autowired
- private JdbcTemplate jdbcTemplate;
-
- @Test
- public void testSpringJdbc() {
- String author = "phenix";
- String date = "2022-08-10";
- String sql = String.format(Locale.ENGLISH, "SELECT" +
- " `id`, `title`, `author`, `content`, `create_time` FROM `article` WHERE" +
- " `author` = '%s' AND `create_time` > '%s'", author, date);
- List<Article> articles = jdbcTemplate.query(sql, (rs, rowNum) -> {
- Timestamp createTimestamp = rs.getTimestamp("create_time");
- LocalDateTime createTime = createTimestamp != null ?
- LocalDateTime.ofInstant(createTimestamp.toInstant(), ZoneId.systemDefault()) : null;
- return Article.builder().id(rs.getInt("id"))
- .author(rs.getString("author"))
- .content(rs.getString("content"))
- .title(rs.getString("title"))
- .createTime(createTime).build();
- });
- articles.forEach(System.out::println);
- }
- }
-
从上面的代码,可以发现 JDBC 的优缺点
创建 resource\hibernate-access\hibernate-config.xml,配置数据库等信息
- <?xml version="1.0" encoding="utf-8"?>
- <!DOCTYPE hibernate-configuration PUBLIC
- "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
- "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
- <hibernate-configuration>
- <session-factory>
- <property name="hibernate.connection.driver_class">
- com.mysql.cj.jdbc.Driver
- </property>
- <property name="hibernate.connection.url">
- jdbc:mysql://localhost:3306/mybatis_demo?useUnicode=true
- </property>
- <property name="hibernate.connection.username">root</property>
- <property name="hibernate.connection.password">******</property>
- <property name="hibernate.dialect">
- org.hibernate.dialect.MySQL5Dialect
- </property>
- <property name="hibernate.show_sql">true</property>
- <mapping resource="chapter01/hibernate-mapping/article.hbm.xml" />
- </session-factory>
- </hibernate-configuration>
-
创建 resource\hibernate-access\hibernate-mapping\article.hbm.xml,做对象与列映射
- <?xml version="1.0" encoding="utf-8"?>
- <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
- "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
-
- <hibernate-mapping>
- <class name="com.phenix.learn.mybatis.chapter01.model.Article" table="article">
- <id name="id" column="id" type="java.lang.Integer">
- <generator class="native"/>
- </id>
- <property name="title" column="title" type="java.lang.String" length="100"/>
- <property name="author" column="author" type="java.lang.String" length="100"/>
- <property name="content" column="content" type="java.lang.String" />
- <property name="createTime" column="create_time" type="java.time.LocalDateTime"/>
- </class>
- </hibernate-mapping>
-
创建 HibernateTest.java,测试 hibernate 访问数据库
- public class HibernateTest {
- private SessionFactory sessionFactory;
-
- @Before
- public void before() {
- Configuration configuration = new Configuration();
- configuration.configure("hibernate-access/hibernate-config.xml");
- sessionFactory = configuration.buildSessionFactory();
- }
-
- @After
- public void after() {
- sessionFactory.close();
- }
-
- @Test
- public void testOrm() {
- System.out.println("-------- ORM Query --------");
- Session session = null;
- try {
- session = sessionFactory.openSession();
- int id = 1;
- Article article = session.get(Article.class, id);
- System.out.println("ORM Query Result:");
- System.out.println(article);
- } finally {
- if (Objects.nonNull(session)) {
- session.close();
- }
- }
- }
-
- @Test
- public void testHql() {
- System.out.println("-------- HQL Query --------");
- Session session = null;
- try {
- session = sessionFactory.openSession();
- String hql = "from Article where author = :author and create_time > :createTime";
- Query query = session.createQuery(hql);
- query.setParameter("author", "phenix");
- query.setParameter("createTime", "2022-08-10");
- System.out.println(query.getQueryString());
- List articles = query.list();
- if (articles != null && articles.size() > 0) {
- articles.forEach(System.out::println);
- }
- } finally {
- if (Objects.nonNull(session)) {
- session.close();
- }
- }
- }
-
- @Test
- public void testJpaCriteria() {
- System.out.println("-------- JPA Query --------");
- Session session = null;
- try {
- session = sessionFactory.openSession();
- CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
-
- CriteriaQuery<Article> criteriaQuery = criteriaBuilder.createQuery(Article.class);
- Root<Article> article = criteriaQuery.from(Article.class);
- Predicate greaterThan = criteriaBuilder.greaterThan(
- article.get("createTime"), LocalDateTime.parse("2022-08-10T00:00:00"));
- Predicate equal = criteriaBuilder
- .equal(article.get("author"), "phenix");
- criteriaQuery.select(article).where(equal, greaterThan);
-
- Query<Article> query = session.createQuery(criteriaQuery);
- List<Article> articles = query.getResultList();
- System.out.println("JPA Criteria Query Result: ");
- articles.forEach(System.out::println);
- } finally {
- if (Objects.nonNull(session)) {
- session.close();
- }
- }
- }
-
- @Test
- public void testSave() {
- System.out.println("-------- ORM Save --------");
- Session session = null;
- try {
- session = sessionFactory.openSession();
- session.beginTransaction();
- Article article = Article.builder().author("author001")
- .title("author001").content("author001").createTime(LocalDateTime.now()).build();
- session.save(article);
- session.getTransaction().commit();
- System.out.println("ORM Save Result: ");
- System.out.println(article);
- System.out.println();
- } finally {
- if (Objects.nonNull(session)) {
- session.close();
- }
- }
- }
- }
-
MyBatis 与 Hibernate 的区别
1、从映射关系上来说, Hibernate 是把实体类( POJO)和表进行了关联,是一种完整的ORM(O/Rmapping)框架;而 MyBatis 则是将数据访问接口( Dao)与 SQL 进行了关联,本质上算是一种 SQL 映射。
2、从使用的角度来说,使用 Hibernate 通常不需要写 SQL,让框架自动生成就即可。但 MyBatis 则不行,再简单的数据库访问操作都需要有与之对应的 SQL。 另一方面,由于 Hibernate 可自动生成 SQL,所以进行数据库移植时,代价要小一点。而由于使用 MyBatis 需要手写 SQL, 加之 SQL 方言的存在。这就导致在进行数据库移植时, 出现SQL 无法正常使用的情况,此时需要根据 SQL 方言修改 SQL。
如何选择 Hibernate 和 MyBatis
Hibernate 可自动生成 SQL,降低使用成本。但同时也要意识到,这样做也是有代价的,会损失灵活性。比如,如果当我们需要手动优化 SQL 时, 会发现很难改变 Hibernate 自动生成的 SQL。因此对于 Hibernate 来说,它适用于一些需求比较稳定,变化比较小的项目,譬如 OA、 CRM 等
与 Hibernate 相反, MyBatis 需要手动维护 SQL,这会增加使用成本。但同时,使用者可灵活控制 SQL 的行为,这为改动和优化 SQL 提供了便利性。所以 MyBatis 适合应用在一些需要快速迭代,需求变化大的项目中,这也就是为什么 MyBatis 在互联网公司中使用的比较广泛的原因。除此之外, MyBatis 还提供了插件机制,使用者可以按需定制插件。这也是MyBatis 灵活性的一个体现。
数据库创建表 author 和 article_v2 表,一个 author 有多条 article,一条 article 对应一个 author。
- CREATE TABLE IF NOT EXISTS `article_v2` (
- `id` int(11) NOT NULL AUTO_INCREMENT,
- `title` varchar(100) DEFAULT NULL,
- `author_id` int(11) DEFAULT 0,
- `type` int(11) default 0,
- `content` text,
- `create_time` datetime DEFAULT NULL,
- PRIMARY KEY (`id`)
- ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
-
- INSERT INTO mybatis_demo.article_v2
- (id, title, author_id, `type`, content, create_time)
- VALUES(1, 'mybatis指南', 1, 4, '这是一本mybatis的使用指南', '2022-08-15 00:00:10.000');
- INSERT INTO mybatis_demo.article_v2
- (id, title, author_id, `type`, content, create_time)
- VALUES(2, 'Dubbo使用指南', 2, 2, '这是一本Dubbo使用指南', '2022-08-15 00:01:10.000');
- INSERT INTO mybatis_demo.article_v2
- (id, title, author_id, `type`, content, create_time)
- VALUES(3, 'JAVA核心编程', 1, 1, '这是一本java大师级的书', '2022-08-15 00:02:10.000');
- INSERT INTO mybatis_demo.article_v2
- (id, title, author_id, `type`, content, create_time)
- VALUES(4, 'Spring5编程', 2, 3, '这是一本Spring5的编程', '2022-08-15 00:03:10.000');
-
- CREATE TABLE IF NOT EXISTS `author` (
- `id` int(11) NOT NULL AUTO_INCREMENT,
- `name` varchar(100) DEFAULT NULL,
- `age` int(11) DEFAULT 0,
- `sex` int(11) DEFAULT 0,
- `email` varchar(100) DEFAULT NULL,
- PRIMARY KEY (`id`)
- ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
-
- INSERT INTO mybatis_demo.author
- (id, name, age, sex, email)
- VALUES(1, '德鲁伊', 40, 0, 'deluyi@163.com');
- INSERT INTO mybatis_demo.author
- (id, name, age, sex, email)
- VALUES(2, '鲁大师', 30, 1, 'ludashi@163.com');
-
加入对象、dao 文件和 mapper 文件
ArticleV2Ao.java
- @Getter
- @Setter
- @ToString
- public class ArticleV2Ao {
- private Integer id;
- private String title;
- private ArticleTypeEnum type;
- private AuthorAo author;
- private String content;
- private LocalDateTime createTime;
- }
-
AuthorAo.java
- @Getter
- @Setter
- @ToString
- public class AuthorAo {
- private Integer id;
- private String name;
- private Integer age;
- private SexEnum sex;
- private String email;
- private List<ArticleV2Ao> articles;
- }
-
ArticleV2Dao.java
- public interface ArticleV2Dao {
- ArticleV2Ao findOne(@Param("id") int id);
- }
-
mybatis-access-2/dao/ArticleV2Mapper.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.phenix.learn.mybatis.dao.ArticleV2Dao">
- <resultMap id="authorResult" type="Author">
- <id property="id" column="author_id"/>
- <result property="name" column="name"/>
- <result property="age" column="age"/>
- <result property="sex" column="sex"
- typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
- <result property="email" column="email"/>
- </resultMap>
-
- <resultMap id="articleResult" type="Article">
- <id property="id" column="id" />
- <result property="type" column="type"
- typeHandler="com.phenix.learn.mybatis.handler.ArticleTypeHandler"/>
- <result property="title" column="title"/>
- <result property="content" column="content"/>
- <result property="createTime" column="create_time"/>
- <association property="author" javaType="Author" resultMap="authorResult" />
- </resultMap>
-
- <select id="findOne" resultMap="articleResult">
- select
- ar.id,
- ar.author_id,
- ar.title,
- ar.type,
- ar.content,
- ar.create_time,
- au.name,
- au.age,
- au.sex,
- au.email
- from
- article_v2 ar,
- author au
- where
- ar.author_id = au.id
- and ar.id = ${id}
- </select>
-
- </mapper>
-
AuthorDao.java
- public interface AuthorDao {
- AuthorAo findOne(@Param("id") int id);
- }
-
mybatis-access-2/dao/AuthorMapper.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.phenix.learn.mybatis.dao.AuthorDao">
- <resultMap id="articleResult" type="Article">
- <id property="id" column="article_id"/>
- <result property="title" column="title"/>
- <result property="type" column="type"
- typeHandler="com.phenix.learn.mybatis.handler.ArticleTypeHandler"/>
- <result property="content" column="content"/>
- <result property="createTime" column="create_time"/>
- </resultMap>
- <resultMap id="authorResult" type="Author">
- <id property="id" column="id"/>
- <result property="name" column="name"/>
- <result property="age" column="age"/>
- <result property="sex" column="sex"
- typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
- <result property="email" column="email"/>
- <collection property="articles" resultMap="articleResult"/>
- </resultMap>
-
- <select id="findOne" resultMap="authorResult">
- select au.id,
- au.name,
- au.age,
- au.sex,
- au.email,
- ar.id as article_id,
- ar.title,
- ar.type,
- ar.content,
- ar.create_time
- from author au,
- article_v2 ar
- where au.id = ar.author_id
- and au.id = ${id}
- </select>
- </mapper>
-
ArticleTypeEnum.java
- @Getter
- public enum ArticleTypeEnum {
- NONE(0, ""),
- JAVA(1, "Java"),
- DUBBO(2, "Dubbo"),
- SPRING(3, "Spring"),
- MYBATIS(4, "MyBatis");
-
- private final int code;
- private final String type;
-
- ArticleTypeEnum(int code, String type) {
- this.code = code;
- this.type = type;
- }
-
- public static ArticleTypeEnum find(int code) {
- return Arrays.stream(ArticleTypeEnum.values()).filter(e -> e.getCode() == code).findFirst().orElse(ArticleTypeEnum.NONE);
- }
- }
-
SexEnum.java
- @Getter
- public enum SexEnum {
- MALE(0),
- FEMALE(1);
-
- private final int sex;
-
- SexEnum(int sex) {
- this.sex = sex;
- }
- }
-
-
这里注意,对于 Author 对象的 sex 使用了 MyBatis 自带的枚举转换器;对于 Article 的 type 字段,使用了自定义的类型处理器 ArticleTypeHandler,其代码如下:
- public class ArticleTypeHandler extends BaseTypeHandler<ArticleTypeEnum> {
- @Override
- public void setNonNullParameter(PreparedStatement preparedStatement, int i, ArticleTypeEnum articleTypeEnum, JdbcType jdbcType) throws SQLException {
- // 获取枚举的 code 值,并设置到 PreparedStatement 中
- preparedStatement.setInt(i, articleTypeEnum.getCode());
- }
-
- @Override
- public ArticleTypeEnum getNullableResult(ResultSet resultSet, String columnName) throws SQLException {
- // 从ResultSet中获取code
- int code = resultSet.getInt(columnName);
- return ArticleTypeEnum.find(code);
- }
-
- @Override
- public ArticleTypeEnum getNullableResult(ResultSet resultSet, int columnIndex) throws SQLException {
- int code = resultSet.getInt(columnIndex);
- return ArticleTypeEnum.find(code);
- }
-
- @Override
- public ArticleTypeEnum getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
- int code = cs.getInt(columnIndex);
- return ArticleTypeEnum.find(code);
- }
- }
-
新增 mybatis 的配置文件 mybatis-access-2/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>
- <properties resource="jdbc.properties"/>
- <typeAliases>
- <typeAlias alias="Article"
- type="com.phenix.learn.mybatis.model.ArticleV2Ao"/>
- <typeAlias alias="Author" type="com.phenix.learn.mybatis.model.AuthorAo"/>
- </typeAliases>
-
- <typeHandlers>
- <typeHandler handler="com.phenix.learn.mybatis.handler.ArticleTypeHandler"
- javaType="com.phenix.learn.mybatis.enums.ArticleTypeEnum"/>
- </typeHandlers>
-
- <environments default="development">
- <environment id="development">
- <transactionManager type="JDBC"/>
- <dataSource type="POOLED">
- <property name="driver" value="${jdbc.driver}"/>
- <property name="url" value="${jdbc.url}"/>
- <property name="username" value="${jdbc.username}"/>
- <property name="password" value="${jdbc.password}"/>
- </dataSource>
- </environment>
- </environments>
- <mappers>
- <mapper resource="mybatis-access-2/dao/ArticleV2Mapper.xml"/>
- <mapper resource="mybatis-access-2/dao/AuthorMapper.xml"/>
- </mappers>
- </configuration>
-
下面通过一个表格简单解释配置中出现的一些标签。
标签名称 | 用途 |
---|---|
properties | 用于配置全局属性,这样在配置文件中,可以通过占位符 ${} 进行属性值配置 |
typeAliases | 用于定义别名。如上所示,这里把 xyz.coolblog.model.ArticleDO 的别名定义为 Article,这 样在 SQL 映射文件中,就可以直接使用别名,而不用每次都输入长长的全限定类名 |
typeHandlers | 用于定义全局的类型处理器,如果这里配置了, SQL 映射文件中就不需要再次进行配置。 前面为了讲解需要,我在 SQL 映射文件中也配置了 ArticleTypeHandler,其实是多余的 |
environments | 用于配置事务,以及数据源 |
mappers | 用于配置 SQL 映射文件的位置信息 |
最后,加入 MybatisTest2.java 进行测试
- public class MybatisTest2 {
- private SqlSessionFactory sqlSessionFactory;
-
- @Before
- public void before() throws IOException {
- String resource = "mybatis-access-2/mybatis-config.xml";
- InputStream inputStream = Resources.getResourceAsStream(resource);
- sqlSessionFactory = new
- SqlSessionFactoryBuilder().build(inputStream);
- inputStream.close();
- }
-
- @Test
- public void testQueryArticle() {
- SqlSession session = sqlSessionFactory.openSession();
- try {
- ArticleV2Dao articleV2Dao = session.getMapper(ArticleV2Dao.class);
- ArticleV2Ao article = articleV2Dao.findOne(1);
- System.out.println(article.toString());
- } finally {
- session.close();
- }
- }
-
- @Test
- public void testQueryAuthor() {
- SqlSession session = sqlSessionFactory.openSession();
- try {
- AuthorDao authorDao = session.getMapper(AuthorDao.class);
- AuthorAo authorAo = authorDao.findOne(1);
- System.out.println(authorAo.toString());
- } finally {
- session.close();
- }
- }
- }
-
注意:对象如果使用了 lombok.Builder,会导致报错,参考连接 Mybatis 异常分析之 org.apache.ibatis.executor.result.ResultMapException。
spring 与 mybatis 是两个框架,将他们整合使用需要依赖 spring-mybatis
pom加入:
- <!-- spring mybatis start -->
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-beans</artifactId>
- <version>${spring.version}</version>
- </dependency>
-
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-context</artifactId>
- <version>${spring.version}</version>
- </dependency>
- <dependency>
- <groupId>org.mybatis</groupId>
- <artifactId>mybatis-spring</artifactId>
- <version>2.0.6</version>
- </dependency>
- <!-- spring mybatis end -->
-
resource 目录创建 spring-mybatis 用于存储配置信息。
加入 spring-mybatis/application-mybatis.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:context="http://www.springframework.org/schema/context"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context-4.2.xsd"
- default-lazy-init="true">
- <context:property-placeholder location="classpath:jdbc.properties"/>
- <!-- 配置数据源 -->
- <bean id="dataSource"
- class="org.apache.ibatis.datasource.pooled.PooledDataSource">
- <property name="driver" value="${jdbc.driver}"/>
- <property name="url" value="${jdbc.url}"/>
- <property name="username" value="${jdbc.username}"/>
- <property name="password" value="${jdbc.password}"/>
- </bean>
-
- <!-- 配置 SqlSessionFactory -->
- <bean id="sqlSessionFactory"
- class="org.mybatis.spring.SqlSessionFactoryBean">
- <property name="configLocation"
- value="classpath:spring-mybatis/mybatis-config.xml"/>
- <!-- 给 SqlSessionFactory 配置数据源,这里引用上面的数据源配置 -->
- <property name="dataSource" ref="dataSource"/>
- <property name="mapperLocations" value="mybatis-access-2/dao/*.xml"/>
- </bean>
-
- <!-- 配置 MapperScannerConfigurer -->
- <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
- <!-- 配置 Dao接口所在包 -->
- <property name="basePackage" value="com.phenix.learn.mybatis.dao" />
- </bean>
- </beans>
-
加入 spring-mybatis/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>
- <settings>
- <setting name="cacheEnabled" value="true"/>
- </settings>
- <typeAliases>
- <typeAlias alias="Article"
- type="com.phenix.learn.mybatis.model.ArticleV2Ao"/>
- <typeAlias alias="Author" type="com.phenix.learn.mybatis.model.AuthorAo"/>
- </typeAliases>
-
- <typeHandlers>
- <typeHandler handler="com.phenix.learn.mybatis.handler.ArticleTypeHandler"
- javaType="com.phenix.learn.mybatis.enums.ArticleTypeEnum"/>
- </typeHandlers>
-
- </configuration>
-
配置已经完成,下面写一点测试代码,创建 java 文件 SpringWithMyBatisTest.java,代码如下:
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration("classpath:spring-mybatis/application-mybatis.xml")
- public class SpringWithMyBatisTest implements ApplicationContextAware {
- private ApplicationContext applicationContext;
- /** 自动注入 AuthorDao,无需再通过 SqlSession 获取 */
- @Autowired
- private AuthorDao authorDao;
-
- @Autowired
- private ArticleV2Dao articleDao;
-
- @Before
- public void printBeanInfo() {
- ListableBeanFactory lbf = applicationContext;
- String[] beanNames = lbf.getBeanDefinitionNames();
- Arrays.sort(beanNames);
- System.out.println("\n--------------☆ bean name ☆-------------");
- Arrays.asList(beanNames).subList(0, 5).forEach(System.out::println);
- AuthorDao authorDao =
- (AuthorDao) applicationContext.getBean("authorDao");
- ArticleV2Dao articleDao =
- (ArticleV2Dao) applicationContext.getBean("articleV2Dao");
- System.out.println("\n-----------☆ bean class info ☆------------");
- System.out.println("AuthorDao Class: " + authorDao.getClass());
- System.out.println("ArticleDao Class: " + articleDao.getClass());
- System.out.println("\n-------xxxx--------xxxx-------xxx--------\n");
- }
-
- @Test
- public void testOne2One() {
- ArticleV2Ao article = articleDao.findOne(1);
- AuthorAo author = article.getAuthor();
- article.setAuthor(null);
- System.out.println("\nauthor info:");
- System.out.println(author);
- System.out.println("\narticles info:");
- System.out.println(article);
- }
-
- @Override
- public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
- this.applicationContext = applicationContext;
- }
- }
-
输出内容如下:
- --------------☆ bean name ☆-------------
- articleDao
- articleV2Dao
- authorDao
- dataSource
- org.mybatis.spring.mapper.MapperScannerConfigurer#0
-
- -----------☆ bean class info ☆------------
- AuthorDao Class: class com.sun.proxy.$Proxy19
- ArticleDao Class: class com.sun.proxy.$Proxy20
-
- -------xxxx--------xxxx-------xxx--------
-
- author info:
- AuthorAo(id=1, name=德鲁伊, age=40, sex=MAN, email=deluyi@163.com, articles=null)
-
- articles info:
- ArticleV2Ao(id=1, title=MyBatis指南, type=MYBATIS, author=null, content=这是一本mybatis的使用指南, createTime=2022-08-15T00:00:10)