MyBatis:一文带你全面了解

目录

1. 概述

1.1 MyBatis简介

1.2 MyBatis历史演变

1.3 MyBatis的优点和局限性

1.3.1 优点

1.3.2 局限性 

2. 入门指南

2.1 安装和配置MyBatis 

2.1.1 MyBatis的安装

2.1.2 数据库连接池的选择 

 2.1.3 MyBatis配置方式

2.1.3.1 基于xml配置文件配置

2.1.3.2 基于yaml配置文件配置

2.1.3.3 基于注解配置文配置

2.2 如何使用MyBatis 

2.2.1 基础CRUD操作 

2.2.1.1 映射文件的编写 

2.2.1.2 Java代码的编写

2.2.2 动态SQL语句的编写

2.2.2.1 if元素

2.2.2.2 where元素

2.2.2.3 foreach元素

2.2.3 插入操作 

2.2.3.1 手动指定ID 

2.2.3.2 数据库自动生成ID 

2.2.4 删除操作

2.2.5 更新操作

2.2.6 多表关联查询

2.2.6.1 一对一关联查询

2.2.6.2 一对多关联查询

2.2.7 一些高级功能

2.2.7.1 缓存机制

2.2.7.2 分页插件

3. MyBatis的高级应用

3.1 MyBatis整合Spring

3.1.1 原理

3.1.2 配置方法

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

3.2.1 基础原理

3.2.2 文件生成策略

3.2.3 使用方式

3.3 代码生成器:MyBatis Plus

3.3.1 MyBatis Plus简介

3.3.2 MyBatis Plus的优点和局限性

3.3.3 MyBatis Plus的使用

3.3.3.1 引入MyBatis Plus

3.3.3.2 自动生成代码

3.3.3.3 Lambda查询

3.3.3.4 分页查询

3.3.3.5 多租户支持

4. 最佳实践

4.1 掌握MyBatis数据表设计

4.1.1 表设计规范

4.1.2 表关系设计

4.2 映射文件编写的要点

4.2.1 SQL语句编写

4.2.2 映射规则编写

4.3 编写高效的SQL语句

4.3.1 避免使用子查询

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

5. 总结

5.1 MyBatis的未来

5.2 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) { // ...

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. 数据库连接池的优化问题。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值