MyBatis:一文带你全面了解

发布时间:2023年12月19日

文章来源:MyBatis:一文带你全面了解 - 知乎

MyBatis:一文带你全面了解

1. 概述

1.1 MyBatis简介

MyBatis是一个基于Java语言的持久层框架,它通过XML描述符或注解将对象与存储过程或SQL语句进行映射,并提供了普通SQL查询、存储过程和高级映射等操作方式,使得操作数据库变得非常方便。

MyBatis是Apache下的一个开源项目,其前身是iBATIS,它在2002年由Clinton Begin首次发布。2010年5月,该项目由iBATIS更名为MyBatis,同时推出了第一版MyBatis 3,在整个持久层框架市场上引起了很大的关注和广泛的应用。

1.2 MyBatis历史演变

在iBATIS项目中,XML描述符是核心并且是唯一的形式,它为开发人员提供了很大的灵活性,然而也产生了一些问题,如繁琐、容易出错等。

在MyBatis 3中,Mapper接口和注解成为了主流的配置方式,XML描述符仍然被支持,但不再是唯一的形式。同时,MyBatis 3大量采用了Java 5.0注解,使得代码更加简洁明了。

1.3 MyBatis的优点和局限性

1.3.1 优点

  • 灵活性高:MyBatis不会对应用程序或数据库的现有设计强加任何影响,开发人员可以使用他们已经熟悉的SQL语句、存储过程和数据库触发器等
  • SQL可控性强:对于复杂查询和多表关联查询时,MyBatis的优势尤为明显,因为可以更加灵活地控制生成的SQL语句,并在需要的情况下针对不同的数据库实现进行优化。
  • 缓存机制好:MyBatis提供了一级缓存和二级缓存,可以有效地减少数据库访问次数,提高响应速度,而且它们的使用非常方便,并且默认情况下处于开启状态。
  • 生态系统完善:MyBatis有着非常强大的社区支持,同时它也与Spring,Spring Boot等流行框架或中间件无缝整合,方便企业级项目的开发。

1.3.2 局限性

  • 性能问题:相比于Hibernate等ORM框架,在大规模数据处理能力和并发性方面,MyBatis的性能表现略逊一筹。
  • 配置复杂:相比Hibernate等ORM框架,MyBatis的配置文件相对较为复杂,需要花费更多的时间和精力进行配置。
  • 映射错误难以追踪:由于映射文件是通过XML描述符或注解进行的,为了解决常见的SQL问题,需要对SQL语句的编写和映射文件的正确描述非常敏感,出现异常时排查起来也较为繁琐。

2. 入门指南

2.1 安装和配置MyBatis

2.1.1 MyBatis的安装

MyBatis的安装十分简单,只需要在项目的pom.xml文件中添加如下依赖即可:

<dependencies>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.7</version>
    </dependency>
</dependencies>

另外,如果需要使用MyBatis Generator来自动生成Java代码和MyBatis映射文件,则还需要添加如下插件:

<plugins>
    <plugin>
        <groupId>org.mybatis.generator</groupId>
        <artifactId>mybatis-generator-maven-plugin</artifactId>
        <version>1.4.0</version>
        <configuration>
            <!-- MyBatis Generator配置文件的位置 -->
            <configurationFile>src/main/resources/generatorConfig.xml</configurationFile>
            <overwrite>true</overwrite>
            <verbose>true</verbose>
        </configuration>
    </plugin>
</plugins>

2.1.2 数据库连接池的选择

MyBatis并没有内置数据库连接池,因此需要使用第三方的数据库连接池。常见的数据库连接池有如下几种:

  • HikariCP:性能最好的连接池,也是目前最流行的连接池之一。
  • Apache Commons DBCP2:Apache官方开发的连接池,支持连接池配置和管理、连接有效性验证、闲置连接回收等功能。
  • Alibaba Druid:阿里巴巴开发的连接池,支持JDBC规范、多数据源、SQL防注入、监控等功能。

在实际使用中,我们可以根据自己的需求选择合适的数据库连接池,这里以HikariCP为例进行演示。可以通过以下方式添加HikariCP的依赖:

<dependencies>
    <dependency>
        <groupId>com.zaxxer</groupId>
        <artifactId>HikariCP</artifactId>
        <version>4.0.3</version>
    </dependency>
</dependencies>

2.1.3 MyBatis配置方式

2.1.3.1 基于xml配置文件配置

MyBatis的配置文件是一个XML文件,包含了MyBatis的大部分配置信息,例如数据库连接信息、映射文件位置、缓存配置等。下面是一个简单的MyBatis配置文件样例:

<?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">
            <transactionManager type="JDBC"/>
            <dataSource type="com.zaxxer.hikari.HikariDataSource">
                <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
                <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test?useSSL=false&amp;serverTimezone=Asia/Shanghai"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <!-- 扫描映射文件 -->
    <mappers>
        <mapper resource="com/example/demo/mapper/UserMapper.xml"/>
    </mappers>
</configuration>

其中,<environments>标签用于指定数据库连接信息,包括事务管理器和数据源信息。这里使用了HikariCP来作为数据源,同时指定了MySQL数据库的连接信息。

<mappers>标签用于指定映射文件的位置,这里指定了一个映射文件,并指定了它的资源路径。

2.1.3.2 基于yaml配置文件配置

MyBatis也支持使用YAML格式来进行配置,相对于XML格式更加简洁直观。以下是一个基于YAML格式配置的样例:

# MyBatis 配置
mybatis:
  # 别名配置
  typeAliasesPackage: com.example.demo.entity
  # Mapper XML文件存放路径
  mapperLocations: classpath*:mapper/*.xml
  # 数据库连接池配置
  datasource:
    url: jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      minimumIdle: 5
      maximumPoolSize: 20
      idleTimeout: 300000
      connectionTimeout: 30000

其中,typeAliasesPackage用于设置实体类的包路径,mapperLocations用于指定Mapper XML文件的位置,datasource用于配置数据库连接池,可以设置连接池的参数。

2.1.3.3 基于注解配置文配置

在MyBatis中,还可以使用注解来进行配置,不再需要XML或YAML格式的配置文件。以下是一个基于注解的样例:

// 实体类
public class User {
    private Long id;
    private String username;
    private Integer age;
    // getter、setter方法省略
}

// Dao接口
@Mapper
public interface UserDao {
    @Select("SELECT * FROM user WHERE id = #{id}")
    User findById(Long id);

    @Insert("INSERT INTO user(username, age) VALUES (#{username}, #{age})")
    int save(User user);

    @Update("UPDATE user SET username = #{username}, age = #{age} WHERE id = #{id}")
    int update(User user);

    @Delete("DELETE FROM user WHERE id = #{id}")
    int deleteById(Long id);
}

// 配置类
@Configuration
@MapperScan("com.example.demo.mapper")
public class MybatisConfig {
    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);

        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sessionFactory.setMapperLocations(resolver.getResources("classpath*:mapper/*.xml"));

        return sessionFactory.getObject();
    }
}

以上代码使用了@Mapper注解来标识Dao接口,并使用@Select@Insert@Update@Delete等注解来进行SQL操作的配置。在MybatisConfig中,使用了@MapperScan注解来指定Mapper类的扫描路径,并使用SqlSessionFactoryBean来进行SqlSessionFactory的配置。

2.2 如何使用MyBatis

2.2.1 基础CRUD操作

2.2.1.1 映射文件的编写

MyBatis的核心是SQL映射语句,而SQL映射语句则是以XML文件的形式维护在项目中。以下是一个简单的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="com.example.demo.mapper.UserMapper">
    <!-- 查询操作 -->
    <select id="findById" parameterType="Long" resultType="User">
        SELECT *
        FROM user
        WHERE id = #{id}
    </select>

    <!-- 插入操作 -->
    <insert id="save" parameterType="User">
        INSERT INTO user(username, age)
        VALUES (#{username}, #{age})
    </insert>

    <!-- 更新操作 -->
    <update id="update" parameterType="User">
        UPDATE user SET
        username = #{username},
        age = #{age}
        WHERE id = #{id}
    </update>

    <!-- 删除操作 -->
    <delete id="deleteById" parameterType="Long">
        DELETE FROM user
        WHERE id = #{id}
    </delete>
</mapper>

其中,<mapper>标签用于定义命名空间,这里指定了com.example.demo.mapper.UserMapper作为命名空间。

<select>标签用于定义查询操作,id属性表示该SQL语句的唯一标识,parameterType表示参数类型,resultType表示返回值类型。在这个例子中,findById是查询用户信息的语句。

<insert><update><delete>标签分别表示插入、更新和删除操作,它们的语法与<select>标签类似。

2.2.1.2 Java代码的编写

有了映射文件之后,我们就可以使用Java代码来进行数据库操作了。

public interface UserMapper {
    // 查询操作
    User findById(Long id);

    // 插入操作
    int save(User user);

    // 更新操作
    int update(User user);

    // 删除操作
    int deleteById(Long id);
}

首先定义一个接口,其中包含几个基本的CRUD操作。

@Mapper
public interface UserMapper {
    User findById(Long id);

    int save(User user);

    int update(User user);

    int deleteById(Long id);
}

然后使用@Mapper注解标识该接口为Mapper接口。MyBatis会自动扫描这些接口并创建对应的实现类。

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public User findById(Long id) {
        return userMapper.findById(id);
    }

    @Override
    public int save(User user) {
        return userMapper.save(user);
    }

    @Override
    public int update(User user) {
        return userMapper.update(user);
    }

    @Override
    public int deleteById(Long id) {
        return userMapper.deleteById(id);
    }
}

最后,在Service层中注入Mapper接口,并使用对应的方法进行数据库操作。

@SpringBootApplication
@MapperScan("com.example.demo.mapper")
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

在Spring Boot主类中加上@MapperScan注解,指定Mapper接口的扫描路径。

通过以上步骤,我们就可以完成基本的CRUD操作。

2.2.2 动态SQL语句的编写

在实际开发中,我们需要根据不同的条件动态生成SQL语句,这时就需要使用MyBatis提供的动态SQL功能。常用的动态SQL元素有ifwhereforeach等。

2.2.2.1 if元素

if元素可以用于根据条件判断是否包含某个SQL语句片段。

例如,我们需要查询年龄大于18岁且小于等于30岁的用户信息,可以这样编写SQL语句:

<select id="findUsersByAge" parameterType="Map" resultType="User">
    SELECT *
    FROM user
    WHERE 1 = 1
    <if test="minAge != null">
        AND age &gt;= #{minAge}
    </if>
    <if test="maxAge != null">
        AND age &lt;= #{maxAge}
    </if>
</select>

以上代码中,<if>元素用于判断minAgemaxAge是否为null,如果不为null,则将对应的SQL语句片段拼接到最终的SQL语句中。

2.2.2.2 where元素

where元素可以用于动态生成WHERE子句,如果所有条件均为null,则不会生成WHERE子句。

例如,我们需要查询用户名和密码匹配的用户信息,可以这样编写SQL语句:

<select id="findUserByUsernameAndPassword" parameterType="Map" resultType="User">
    SELECT *
    FROM user
    <where>
        <if test="username != null">
            AND username = #{username}
        </if>
        <if test="password != null">
            AND password = #{password}
        </if>
    </where>
</select>

以上代码中,<where>元素用于动态生成WHERE子句,如果usernamepassword均为null,则不会生成WHERE子句。

2.2.2.3 foreach元素

foreach元素可以用于循环遍历一个集合,并将集合中的元素拼接到SQL语句中。

例如,我们需要查询多个用户信息,可以这样编写SQL语句:

<select id="findUsersByIds" parameterType="List" resultType="User">
    SELECT *
    FROM user
    WHERE id IN
    <foreach collection="list" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>

以上代码中,<foreach>元素用于循环遍历List类型的参数,并将集合中的元素拼接到SQL语句中。

2.2.3 插入操作

在MyBatis中,插入操作分为手动指定ID和数据库自动生成ID两种方式。

2.2.3.1 手动指定ID

如果需要手动指定ID,可以这样编写SQL语句:

<insert id="save" parameterType="User">
    INSERT INTO user(id, username, password, age)
    VALUES (#{id}, #{username}, #{password}, #{age})
</insert>

以上代码中,将插入的ID值直接作为参数传入插入语句中。

2.2.3.2 数据库自动生成ID

如果需要数据库自动生成ID,可以这样编写SQL语句:

<insert id="save" parameterType="User" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO user(username, password, age)
    VALUES (#{username}, #{password}, #{age})
</insert>

以上代码中,通过useGeneratedKeys="true"参数告诉MyBatis要求数据库生成主键,并通过keyProperty="id"参数指定了主键的属性名。这样,当执行插入操作后,主键值将自动赋值到User对象的id属性中。

2.2.4 删除操作

删除操作比较简单,可以这样编写SQL语句:

<delete id="deleteById" parameterType="Long">
    DELETE FROM user
    WHERE id = #{id}
</delete>

以上代码中,直接通过传入的id参数进行删除操作。

2.2.5 更新操作

更新操作也比较简单,可以这样编写SQL语句:

<update id="update" parameterType="User">
    UPDATE user SET
    username = #{username},
    password = #{password},
    age = #{age}
    WHERE id = #{id}
</update>

以上代码中,通过传入的User对象进行更新操作。

2.2.6 多表关联查询

在实际应用中,常常需要进行多表关联查询,MyBatis提供了<association><collection>标签来完成多表关联查询。

2.2.6.1 一对一关联查询

例如,我们有两个表,分别是user表和card表,每个用户都有一张银行卡,通过userId列可以进行关联查询。可以这样编写SQL语句:

<select id="findUsersWithCards" resultType="User">
    SELECT u.*, c.*
    FROM user u
    INNER JOIN card c ON u.id = c.userId
</select>

以上代码中,通过INNER JOIN连接两个表,并使用u.*c.*来选择需要查询的列。

然而,直接返回结果集会将所有数据都映射到User对象中,并不符合我们的需求。此时,可以使用<resultMap>标签来自定义结果映射规则。

<resultMap id="userMap" type="User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <result property="password" column="password"/>
    <result property="age" column="age"/>
    <association property="card" javaType="Card">
        <id property="id" column="cardId"/>
        <result property="cardCode" column="cardCode"/>
        <result property="balance" column="balance"/>
        <result property="userId" column="userId"/>
    </association>
</resultMap>

<select id="findUsersWithCards" resultMap="userMap">
    SELECT u.id, u.username, u.password, u.age, c.id AS cardId, c.cardCode, c.balance, c.userId
    FROM user u
    INNER JOIN card c ON u.id = c.userId
</select>

以上代码中,<resultMap>标签定义了结果映射规则,包括主键、普通属性和关联属性。其中,<association>标签指定了一个一对一关联关系,并通过property属性指向User对象中的Card属性。

2.2.6.2 一对多关联查询

例如,我们有两个表,分别是user表和address表,每个用户可以有多个地址,通过userId列可以进行关联查询。可以这样编写SQL语句:

<select id="findUsersWithAddresses" resultMap="userMap">
    SELECT u.*, a.*
    FROM user u
    INNER JOIN address a ON u.id = a.userId
</select>

以上代码中,同样使用INNER JOIN连接两个表。

然而,直接返回结果集会将所有数据都映射到User对象中,并不符合我们的需求。此时,可以使用<collection>标签来指定一个一对多关联关系,并通过property属性指向User对象中的addresses属性。

<resultMap id="userMap" type="User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <result property="password" column="password"/>
    <result property="age" column="age"/>
    <collection property="addresses" ofType="Address">
        <id property="id" column="id"/>
        <result property="address" column="address"/>
        <result property="userId" column="userId"/>
    </collection>
</resultMap>

<select id="findUsersWithAddresses" resultMap="userMap">
    SELECT u.id, u.username, u.password, u.age, a.id, a.address, a.userId
    FROM user u
    INNER JOIN address a ON u.id = a.userId
</select>

以上代码中,<collection>标签指定了一个一对多关联关系,并通过ofType属性指定目标类型为Address

2.2.7 一些高级功能

2.2.7.1 缓存机制

MyBatis提供了缓存机制,这可以有效地减少与数据库的交互次数,提高系统性能。

默认情况下,MyBatis会开启一级缓存(SqlSession级别的缓存)和二级缓存(全局共享的缓存)。

<!-- 配置全局二级缓存 -->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

以上代码中,通过配置<cache>标签来启用全局二级缓存,并指定了使用Ehcache作为缓存实现方式。

在自定义Mapper接口中,可以通过@CacheNamespace注解来启用单独的二级缓存。

@CacheNamespace
public interface UserMapper {
    // ...
}

以上代码中,通过@CacheNamespace注解来启用UserMapper的二级缓存。

需要注意的是,如果在进行insert、update、delete等操作时,MyBatis会清空该namespace下的所有缓存。

2.2.7.2 分页插件

MyBatis提供了分页插件,这可以方便地实现分页查询功能。

首先,引入分页插件jar包,例如使用PageHelper插件:

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.11</version>
</dependency>

然后,在MyBatis配置文件中配置分页插件:

<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"/>
</plugins>

最后,在自定义Mapper接口方法中使用分页插件来完成分页查询:

public interface UserMapper {
    List<User> findUsersByPage(@Param("pageNum") int pageNum, @Param("pageSize") int pageSize);
}

以上代码中,使用@Param注解来指定传入的参数名,使用PageHelper插件来实现分页查询。

@Test
public void testFindUsersByPage() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try {
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        PageHelper.startPage(2, 3);
        List<User> users = userMapper.findUsersByPage();
        for (User user : users) {
            System.out.println(user);
        }
        PageInfo<User> pageInfo = new PageInfo<>(users);
        System.out.println("当前页:" + pageInfo.getPageNum());
        System.out.println("每页记录数:" + pageInfo.getPageSize());
        System.out.println("总记录数:" + pageInfo.getTotal());
        System.out.println("总页数:" + pageInfo.getPages());
        System.out.println("是否第一页:" + pageInfo.isIsFirstPage());
        System.out.println("是否最后一页:" + pageInfo.isIsLastPage());
    } finally {
        sqlSession.close();
    }
}

以上代码中,使用PageHelper.startPage()方法来指定分页查询的页码和每页记录数,使用PageInfo类来获取分页相关信息。

3. MyBatis的高级应用

3.1 MyBatis整合Spring

3.1.1 原理

MyBatis和Spring的整合,可以通过Spring提供的SqlSessionFactoryBeanMapperScannerConfigurer来实现。

其中,SqlSessionFactoryBean负责创建SqlSessionFactory对象,MapperScannerConfigurer负责将Mapper接口扫描注册到Spring容器中,以便在应用中注入并使用。

3.1.2 配置方法

首先,引入MyBatis和Spring相关jar包,例如:

<!-- MyBatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
</dependency>
<!-- Spring -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.6</version>
</dependency>

然后,在Spring配置文件中配置SqlSessionFactoryBeanMapperScannerConfigurer

<!-- 配置SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>

<!-- 注册Mapper接口 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.example.mapper"/>
</bean>

以上代码中,通过SqlSessionFactoryBean配置SqlSessionFactory对象,并指定数据源和MyBatis配置文件路径。通过MapperScannerConfigurer注册Mapper接口,其中basePackage指定了Mapper接口所在的包路径。

最后,在需要使用Mapper接口的地方,注入该接口,并使用。

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public User getUserById(int id) {
        return userMapper.getUserById(id);
    }
}

以上代码中,通过@Autowired注解将UserMapper接口注入到UserServiceImpl中,并在方法实现中使用该接口。

3.2 MyBatis Generator——自动化生成MyBatis代码

3.2.1 基础原理

MyBatis Generator是MyBatis官方提供的一个开源项目,可以根据数据库表自动生成对应的Java实体类、Mapper接口和XML文件,极大地简化了开发工作。

具体来说,MyBatis Generator会根据指定的数据库连接信息、表名规则、生成策略等参数,自动生成Java实体类和Mapper接口。同时,还可以根据表结构自动生成SQL语句,并将其配置到XML文件中,以便直接使用。

3.2.2 文件生成策略

通常情况下,MyBatis Generator会为每个表生成3个文件:

  1. Java实体类文件:默认位于src/main/java下的指定包路径中。
  2. Mapper接口文件:默认位于src/main/java下的指定包路径中。
  3. XML文件:默认位于src/main/resources下的指定包路径中。

在生成XML文件时,MyBatis Generator提供了不同的生成策略,如:

  1. XML文件与Java类放在一起,以*Mapper.xml命名。
  2. XML文件单独放置,以*Mapper.xml命名,并在Java类中通过@Mapper注解来指定XML文件名。

3.2.3 使用方式

首先,引入MyBatis Generator相关jar包和数据库驱动,例如:

<!-- MyBatis Generator -->
<dependency>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-core</artifactId>
    <version>1.4.0</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.24</version>
</dependency>

然后,在项目根目录下新建一个generatorConfig.xml配置文件,其中包含数据库连接信息、表名规则、生成策略、Java类型映射等。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>

    <!-- 数据库连接信息 -->
    <context id="mysql" targetRuntime="MyBatis3">
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/test?useSSL=false&amp;serverTimezone=UTC"
                        userId="root"
                        password="root"/>

        <!-- 表名规则 -->
        <table tableName="user" domainObjectName="User"/>

        <!-- Java类型映射 -->
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>

        <!-- Mapper接口生成策略 -->
        <javaClientGenerator targetPackage="com.example.mapper"
                             targetProject="src/main/java"
                             type="XMLMAPPER"/>

        <!-- XML文件生成策略 -->
        <sqlMapGenerator targetPackage="mapper"
                         targetProject="src/main/resources"
                         type="XML"/>

        <!-- Java实体类生成策略 -->
        <javaModelGenerator targetPackage="com.example.entity"
                            targetProject="src/main/java"
                            enableSubPackages="true"
                            xmlAccessor="PUBLIC"/>

        <!-- 表字段和Java属性名映射 -->
        <tableFieldOverride column="id" property="id" />
        <tableFieldOverride column="username" property="username" />
        <tableFieldOverride column="password" property="password" />
        <tableFieldOverride column="age" property="age" />

    </context>
</generatorConfiguration>

以上代码中,通过jdbcConnection指定数据库连接信息。使用<table>标签指定要生成Java实体类和Mapper接口的表,其中tableName指定表名,domainObjectName指定Java实体类名。

通过<javaTypeResolver>指定Java类型映射,例如将bigint映射到Long类型。通过<javaClientGenerator><sqlMapGenerator><javaModelGenerator>分别设置Java实体类生成路径、Mapper接口生成路径和XML文件生成路径及文件名。

最后,在根目录下打开命令行窗口,执行以下命令即可自动生成代码:

$ java -jar mybatis-generator-core-x.x.x.jar -configfile generatorConfig.xml -overwrite

其中,mybatis-generator-core-x.x.x.jar为MyBatis Generator的jar包。-configfile参数指定配置文件名,-overwrite参数表示覆盖已有文件。执行完成后,即可在指定路径下看到生成的代码文件。

示例:使用MyBatis Generator生成Java实体类、Mapper接口和XML文件,假设有一张名为account的表,该表包含id、name和balance三个字段。

首先,在generatorConfig.xml配置文件中添加以下代码:

<context id="mysql" targetRuntime="MyBatis3">
    <!-- ... -->

    <!-- 表名规则 -->
    <table tableName="account" domainObjectName="Account"/>

    <!-- ... -->
</context>

然后,在根目录下打开命令行窗口,执行以下命令:

$ java -jar mybatis-generator-core-x.x.x.jar -configfile generatorConfig.xml -overwrite

执行完成后,可以在指定路径下看到生成的代码文件:

  1. Java实体类:com.example.entity.Account.java
  2. Mapper接口:com.example.mapper.AccountMapper.java
  3. XML文件:mapper/AccountMapper.xml

3.3 代码生成器:MyBatis Plus

3.3.1 MyBatis Plus简介

MyBatis Plus是一款MyBatis框架的增强工具,提供了很多实用的功能,可以极大地简化开发工作。其主要功能包括:

  • 支持CRUD操作:提供了通用的Mapper接口及其实现,可以减少Mapper接口的编写,简化CRUD操作。
  • 自动生成代码:提供了代码生成器,可以根据数据库表自动生成对应的Java实体类、Mapper接口及其XML文件。
  • Lambda查询:提供了Lambda表达式查询功能,可以更方便地进行复杂的查询。
  • 分页查询:提供了分页插件,可以轻松实现分页查询。
  • 多租户支持:提供了多租户插件,可以支持多租户场景。

3.3.2 MyBatis Plus的优点和局限性

MyBatis Plus的主要优点有:

  1. 简化开发:提供了很多实用的功能,并且用法简单,可以极大地简化开发工作。
  2. 提高效率:提供了自动生成代码的功能,可以提高开发效率。
  3. 提高代码可读性:使用Lambda表达式进行查询,可以使查询语句更加直观易懂。
  4. 提高代码可维护性:提供了通用的Mapper接口及其实现,可以减少Mapper接口的编写,简化CRUD操作。

MyBatis Plus的局限性主要有:

  1. 依赖关系较强:需要整合Spring框架和MyBatis框架,对于初学者来说可能不易上手。
  2. 自动化生成的代码不够灵活:自动生成的代码一般不适合特定的需求,需要根据实际需要进行二次开发。

3.3.3 MyBatis Plus的使用

3.3.3.1 引入MyBatis Plus

首先,在项目中引入MyBatis Plus相关的jar包:

<!-- MyBatis Plus -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.5</version>
</dependency>

3.3.3.2 自动生成代码

MyBatis Plus提供了官方的代码生成器,可以根据数据库表自动生成Java实体类、Mapper接口及其XML文件。使用方法如下:

  1. application.properties配置文件中,添加以下代码:

```properties # 数据库连接信息 spring.datasource.url=jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC&characterEncoding=utf-8 spring.datasource.username=root spring.datasource.password=root

# MyBatis Plus配置 mybatis-plus.mapper-locations=classpath:mapper/*.xml mybatis-plus.type-aliases-package=com.example.entity mybatis-plus.configuration.cache-enabled=false mybatis-plus.global-config.id-type=auto ```

其中,spring.datasource.url为数据库连接信息,mybatis-plus.mapper-locations表示Mapper接口对应的XML文件所在路径,mybatis-plus.type-aliases-package表示Java实体类所在包路径。

  1. 创建代码生成器,并配置相应参数,例如:

```java public class CodeGenerator { public static void main(String[] args) { // 数据源配置 DataSourceConfig dataSourceConfig = new DataSourceConfig(); dataSourceConfig.setDbType(DbType.MYSQL) .setUrl("jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC&characterEncoding=utf-8") .setUsername("root") .setPassword("root") .setDriverName("com.mysql.cj.jdbc.Driver");

// 全局配置
       GlobalConfig globalConfig = new GlobalConfig();
       globalConfig.setOutputDir(System.getProperty("user.dir") + "/src/main/java")
               .setAuthor("binjie09")
               .setOpen(false)
               .setFileOverride(true)
               .setIdType(IdType.AUTO)
               .setBaseResultMap(true)
               .setBaseColumnList(true);

       // 包配置
       PackageConfig packageConfig = new PackageConfig();
       packageConfig.setParent("com.example")
               .setEntity("entity")
               .setMapper("mapper")
               .setXml("mapper.xml")
               .setService("service")
               .setServiceImpl("service.impl")
               .setController("controller");

       // 策略配置
       StrategyConfig strategyConfig = new StrategyConfig();
       strategyConfig.setCapitalMode(true)
               .setNaming(NamingStrategy.underline_to_camel)
               .setTablePrefix("t_")
               .setInclude("user");

       // 代码生成器
       AutoGenerator autoGenerator = new AutoGenerator();
       autoGenerator.setDataSource(dataSourceConfig)
               .setGlobalConfig(globalConfig)
               .setPackageInfo(packageConfig)
               .setStrategy(strategyConfig)
               .execute();
   }

} ```

其中,dataSourceConfig为数据源配置,globalConfig为全局配置,packageConfig为包配置,strategyConfig为策略配置。创建了相应的配置后,调用AutoGeneratorexecute()方法即可生成代码。

在以上代码中,setInclude("user")表示只生成user表对应的Java实体类、Mapper接口及其XML文件,也可不传入参数表示生成所有表的代码。

  1. 执行代码生成器即可:

```java public class CodeGenerator { public static void main(String[] args) { // ...

// 执行生成器
       autoGenerator.execute();
   }

} ```

3.3.3.3 Lambda查询

使用MyBatis Plus进行Lambda表达式查询,需要先引入LambdaQueryWrapper类,例如:

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;

然后,可以进行如下Lambda表达式查询:

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public List<User> listUsersByName(String name) {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(User::getName, name);
        return userMapper.selectList(queryWrapper);
    }
}

以上代码中,queryWrapper.like(User::getName, name)表示查询name字段包含name字符串的记录,而User::getName表示获取User对象的name属性。

3.3.3.4 分页查询

使用MyBatis Plus进行分页查询可以很简单地实现,例如:

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public IPage<User> listUsersByPage(int pageNum, int pageSize) {
        Page<User> page = new Page<>(pageNum, pageSize);
        return userMapper.selectPage(page, null);
    }
}

以上代码中,通过new Page<>(pageNum, pageSize)创建了分页对象,即第pageNum页,每页展示pageSize条数据。然后,调用IPage<User> selectPage(IPage<User> page, Wrapper<User> queryWrapper)方法进行分页查询。

3.3.3.5 多租户支持

MyBatis Plus提供了多租户插件,可以轻松实现多租户场景。首先,在application.yml配置文件中添加以下代码:

mybatis-plus:
  configuration:
    # 指定多租户插件类
    plugins: com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor
  global-config:
    # 指定租户ID列名
    tenant-id-column: tenant_id
    # 指定租户ID处理器
    tenant-handler: com.example.handler.MultiTenantHandler

其中,com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor为多租户插件类,tenant-id-column指定租户ID列名,tenant-handler指定租户ID处理器。

然后,在MultiTenantHandler中编写租户ID过滤逻辑,例如:

public class MultiTenantHandler implements TenantHandler {

    private static final String DEFAULT_TENANT_ID = "1";

    @Override
    public Expression getTenantId(boolean where) {
        // 获取当前租户ID,可以从Session、ThreadLocal等获取
        String tenantId = TenantContext.getTenantId();
        // 如果没有获取到租户ID,则使用默认的租户ID
        if (tenantId == null || "".equals(tenantId.trim())) {
            tenantId = DEFAULT_TENANT_ID;
        }
        return new LongValue(tenantId);
    }

    @Override
    public String getTenantIdColumn() {
        return "tenant_id";
    }

    @Override
    public boolean doTableFilter(String tableName) {
        // 过滤掉不需要进行租户ID过滤的表
        return !"user".equalsIgnoreCase(tableName);
    }
}

以上代码中,getTenantId()方法用于获取当前租户ID,getTenantIdColumn()方法返回租户ID列名,doTableFilter()方法用于过滤掉不需要进行租户ID过滤的表。

最后,在Mapper接口的查询语句中添加@SqlParser(filter=true)注解即可启用多租户插件,例如:

public class MultiTenantHandler implements TenantHandler {

    private static final String DEFAULT_TENANT_ID = "1";

    @Override
    public Expression getTenantId(boolean where) {
        // 获取当前租户ID,可以从Session、ThreadLocal等获取
        String tenantId = TenantContext.getTenantId();
        // 如果没有获取到租户ID,则使用默认的租户ID
        if (tenantId == null || "".equals(tenantId.trim())) {
            tenantId = DEFAULT_TENANT_ID;
        }
        return new LongValue(tenantId);
    }

    @Override
    public String getTenantIdColumn() {
        return "tenant_id";
    }

    @Override
    public boolean doTableFilter(String tableName) {
        // 过滤掉不需要进行租户ID过滤的表
        return !"user".equalsIgnoreCase(tableName);
    }
}

以上代码中,getTenantId()方法用于获取当前租户ID,getTenantIdColumn()方法返回租户ID列名,doTableFilter()方法用于过滤掉不需要进行租户ID过滤的表。

最后,在Mapper接口的查询语句中添加@SqlParser(filter=true)注解即可启用多租户插件,例如:

@Mapper
public interface UserMapper extends BaseMapper<User> {

    @SqlParser(filter=true)
    @Override
    List<User> selectList(@Param(Constants.WRAPPER) Wrapper<User> queryWrapper);

    // ...
}

以上代码中,@SqlParser(filter=true)注解表示启用过滤器,对selectList()方法进行租户ID过滤。

4. 最佳实践

4.1 掌握MyBatis数据表设计

在使用MyBatis进行开发之前,首先要掌握好数据表的设计。好的数据表设计可以极大地提高查询效率,降低系统复杂度。下面介绍一些数据表设计的要点。

4.1.1 表设计规范

  1. 表名命名规范:表名使用小写字母,单词之间用下划线隔开,例如user_info
  2. 字段名命名规范:字段名同样使用小写字母,单词之间用下划线隔开,例如user_id
  3. 主键命名规范:主键名统一使用id
  4. 数据类型选取规范:MySQL支持多种数据类型,需要根据实际情况选择合适的数据类型。对于字符串类型,如果长度不确定,建议选择VARCHAR(255),而不是TEXT或者MEDIUMTEXT
  5. 字段注释规范:为每个字段添加注释,说明该字段的含义、取值范围等。
  6. 索引规范:为常用的查询字段创建索引,可以提高查询效率。但是过多的索引会降低写入性能。

4.1.2 表关系设计

在设计数据表时,需要考虑好表与表之间的关系,包括一对一、一对多和多对多等关系。下面介绍一些常见的表关系设计。

  1. 一对一关系:将主键作为外键,存放在从表中。例如,在一个订单系统中,一个订单只属于一个用户,一个用户只能拥有一个订单,这就是一对一关系。
  2. 一对多关系:将主键作为外键,存放在从表中。例如,在一个博客系统中,一个文章可以有多个评论,一个评论只能属于一个文章,这就是一对多关系。
  3. 多对多关系:需要使用中间表来维护关系,中间表存储两个表的主键,例如,在一个班级系统中,一个学生可以属于多个班级,一个班级可以拥有多个学生,这就是多对多关系。

4.2 映射文件编写的要点

MyBatis的映射文件用于定义SQL语句和映射规则,下面介绍一些映射文件编写的要点。

4.2.1 SQL语句编写

  1. 避免使用SELECT *:应该明确指出需要查询的字段,而不是查询全部字段。
  2. 注意参数类型:在使用Mapper接口调用SQL查询时,参数类型必须与Mapper接口方法参数类型一致或者符合JavaBean规范。
  3. 使用动态SQL:可以根据不同的条件排除或者包含某个SQL片段,以达到避免重复代码的作用。
  4. 使用别名:在多个表或者多个字段具有相同名称时,需要使用别名来消除歧义,提高可读性。

4.2.2 映射规则编写

  1. 映射关系标签:MyBatis提供了很多映射关系标签,例如<resultMap><association><collection>等。需要根据不同情况选择合适的映射关系标签。
  2. 映射ID命名:在定义映射ID时,应该使用规范的命名方式,例如getUserByIdlistUsers等。
  3. 属性配置:可以为映射属性添加配置,例如添加not-null="true"表示该属性不能为空。
  4. 鉴别器:对于一些复杂的查询语句,需要使用鉴别器(discriminator)进行分发处理,示例代码如下:
<resultMap id="userMap" type="User">
    <id column="id" property="id"/>
    <result column="username" property="username"/>

    <discriminator javaType="int" column="type">
        <case value="1" resultMap="studentMap"/>
        <case value="2" resultMap="teacherMap"/>
    </discriminator>
</resultMap>

<resultMap id="studentMap" type="Student">
    <result column="score" property="score"/>
</resultMap>

<resultMap id="teacherMap" type="Teacher">
    <result column="title" property="title"/>
</resultMap>

以上代码中,使用<discriminator>标签进行分发处理,当查询结果中的type列值为1时,使用studentMap映射;当type列值为2时,使用teacherMap映射。

4.3 编写高效的SQL语句

4.3.1 避免使用子查询

子查询指的是将一个SQL语句嵌套到另一个SQL语句中,作为另一个SQL语句的一部分进行查询操作。虽然使用子查询可以实现较为复杂的查询操作,但是也会带来一些性能问题,主要表现在以下几个方面:

  1. 子查询会增加数据库的负载,导致查询速度变慢。
  2. 子查询可能需要重复执行多次,而每次执行都需要进行一次完整的查询操作,从而造成额外的开销。
  3. 子查询可能会引起死锁或者线程阻塞等问题,从而降低数据库的并发性能。

因此,在编写SQL语句时,应该尽可能避免使用子查询。可以通过以下几种方式来替代使用子查询的情况:

  1. 使用多表连接查询:使用多表连接查询可以避免使用子查询,通过将多个表的数据进行连接,完成一次完整的查询操作。
  2. 使用内置函数:数据库提供了许多内置函数,可以用于实现一些高级的查询操作,例如聚合函数、数学函数、字符串函数等。使用内置函数可以减少查询复杂度,避免使用子查询。
  3. 使用临时表:如果必须使用子查询,可以尝试使用临时表。先将子查询的结果存储到一个临时表中,然后在主查询操作中使用该临时表,可以避免子查询多次执行。

以下是一个使用多表连接查询替代子查询的示例:

-- 子查询
SELECT * FROM orders WHERE customer_id IN (SELECT id FROM customers WHERE name LIKE '%张三%');

-- 多表连接查询
SELECT o.* FROM orders o JOIN customers c ON o.customer_id = c.id WHERE c.name LIKE '%张三%';

4.3.2 避免使用%前缀模糊查询

%前缀模糊查询指的是在查询字符串时,使用%作为前缀进行模糊匹配。虽然使用%前缀模糊查询可以实现快速查找匹配字符串的功能,但同时也会带来一些性能问题,主要表现在以下几方面:

  1. %前缀模糊查询会增加数据库的负载,导致查询速度变慢。
  2. %前缀模糊查询可能需要对整个表进行扫描,而这将是一个非常耗时的操作。
  3. %前缀模糊查询不利于索引的使用,因为%前缀模糊查询无法使用普通的B树索引。

因此,在编写SQL语句时,应该尽可能避免使用%前缀模糊查询。可以通过以下方式来替代使用%前缀模糊查询的情况:

  1. 使用全文索引:数据库提供了全文索引功能,可以用于实现全文搜索。使用全文索引可以取代%前缀模糊查询,提高查询效率。
  2. 使用后缀模糊查询:如果必须进行模糊查询,可以尝试使用后缀模糊查询。后缀模糊查询会优先使用索引,从而提高查询效率。
  3. 尽可能精确匹配:在编写查询条件时,应该尽可能精确匹配,避免使用过于宽泛的查询条件。

以下是一个使用全文索引替代%前缀模糊查询的示例:

-- %前缀模糊查询
SELECT * FROM articles WHERE title LIKE '%关键词%';

-- 全文索引查询
SELECT * FROM articles WHERE MATCH(title) AGAINST('关键词' IN NATURAL LANGUAGE MODE);

5. 总结

5.1 MyBatis的未来

MyBatis作为一款优秀的ORM框架,其使用广泛,功能也非常强大。MyBatis的未来发展方向主要包括以下几个方面:

  1. 持续推进版本更新:MyBatis在不断地更新和迭代,新版本不仅修复了一些已知问题,还引入了一些新特性和优化,比如在3.x版本中引入了Spring Boot Starter,提供了更便捷的集成方式。
  2. 加强与其他组件的整合:MyBatis可以与Spring Framework等组件紧密配合,形成完善的应用架构。
  3. 多数据源支持:MyBatis在多数据源的支持上也越来越完善,可以针对不同的数据源进行不同的操作。
  4. 引入更多的插件:MyBatis插件是其扩展功能的重要手段,可以在SQL语句执行前、后进行一系列的自定义处理。未来MyBatis可能会开发更多的插件,以提高开发效率和使用体验。

5.2 MyBatis的注意事项以及常见问题

在使用MyBatis时,需要注意以下事项:

  1. 避免使用SELECT *:使用明确指出需要查询的字段,而不是查询全部字段,可以提高查询效率。
  2. 显式指定参数类型:在使用Mapper接口调用SQL查询时,参数类型必须与Mapper接口方法参数类型一致或者符合JavaBean规范。
  3. 使用动态SQL:可以根据不同的条件排除或者包含某个SQL片段,以达到避免重复代码的作用。
  4. 使用缓存:MyBatis支持多种缓存方式,可以提高查询效率。但是需要注意缓存更新策略,以免出现数据不一致问题。
  5. 配置文件管理:MyBatis的配置文件中包含了许多的配置信息,需要注意配置信息的管理和维护。

在使用MyBatis过程中,也存在一些常见问题,例如:

  1. 多表查询使用联合查询的性能问题。
  2. 分页查询时,使用游标或者取所有数据的性能问题。
  3. MyBatis缓存机制带来的数据不一致问题。
  4. SQL语句过于复杂,导致难以调试和维护。
  5. 数据库连接池的优化问题。
文章来源:https://blog.csdn.net/qq_57570052/article/details/135080921
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。