文章来源: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&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/Shanghaiusername: rootpassword: rootdriver-class-name: com.mysql.jdbc.Drivertype: com.zaxxer.hikari.HikariDataSourcehikari:minimumIdle: 5maximumPoolSize: 20idleTimeout: 300000connectionTimeout: 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 {@Beanpublic 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 userWHERE id = #{id}</select><!-- 插入操作 --><insert id="save" parameterType="User">INSERT INTO user(username, age)VALUES (#{username}, #{age})</insert><!-- 更新操作 --><update id="update" parameterType="User">UPDATE user SETusername = #{username},age = #{age}WHERE id = #{id}</update><!-- 删除操作 --><delete id="deleteById" parameterType="Long">DELETE FROM userWHERE 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 {@Autowiredprivate UserMapper userMapper;@Overridepublic User findById(Long id) {return userMapper.findById(id);}@Overridepublic int save(User user) {return userMapper.save(user);}@Overridepublic int update(User user) {return userMapper.update(user);}@Overridepublic 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元素有if
、where
、foreach
等。
2.2.2.1 if元素
if
元素可以用于根据条件判断是否包含某个SQL语句片段。
例如,我们需要查询年龄大于18岁且小于等于30岁的用户信息,可以这样编写SQL语句:
<select id="findUsersByAge" parameterType="Map" resultType="User">SELECT *FROM userWHERE 1 = 1<if test="minAge != null">AND age >= #{minAge}</if><if test="maxAge != null">AND age <= #{maxAge}</if>
</select>
以上代码中,<if>
元素用于判断minAge
和maxAge
是否为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
子句,如果username
和password
均为null,则不会生成WHERE
子句。
2.2.2.3 foreach元素
foreach
元素可以用于循环遍历一个集合,并将集合中的元素拼接到SQL语句中。
例如,我们需要查询多个用户信息,可以这样编写SQL语句:
<select id="findUsersByIds" parameterType="List" resultType="User">SELECT *FROM userWHERE 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 userWHERE id = #{id}
</delete>
以上代码中,直接通过传入的id
参数进行删除操作。
2.2.5 更新操作
更新操作也比较简单,可以这样编写SQL语句:
<update id="update" parameterType="User">UPDATE user SETusername = #{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 uINNER 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.userIdFROM user uINNER 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 uINNER 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.userIdFROM user uINNER 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提供的SqlSessionFactoryBean
和MapperScannerConfigurer
来实现。
其中,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配置文件中配置SqlSessionFactoryBean
和MapperScannerConfigurer
:
<!-- 配置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 {@Autowiredprivate UserMapper userMapper;@Overridepublic 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个文件:
- Java实体类文件:默认位于
src/main/java
下的指定包路径中。 - Mapper接口文件:默认位于
src/main/java
下的指定包路径中。 - XML文件:默认位于
src/main/resources
下的指定包路径中。
在生成XML文件时,MyBatis Generator提供了不同的生成策略,如:
- XML文件与Java类放在一起,以
*Mapper.xml
命名。 - 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 generatorConfigurationPUBLIC "-//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&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
执行完成后,可以在指定路径下看到生成的代码文件:
- Java实体类:
com.example.entity.Account.java
- Mapper接口:
com.example.mapper.AccountMapper.java
- 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的主要优点有:
- 简化开发:提供了很多实用的功能,并且用法简单,可以极大地简化开发工作。
- 提高效率:提供了自动生成代码的功能,可以提高开发效率。
- 提高代码可读性:使用Lambda表达式进行查询,可以使查询语句更加直观易懂。
- 提高代码可维护性:提供了通用的Mapper接口及其实现,可以减少Mapper接口的编写,简化CRUD操作。
MyBatis Plus的局限性主要有:
- 依赖关系较强:需要整合Spring框架和MyBatis框架,对于初学者来说可能不易上手。
- 自动化生成的代码不够灵活:自动生成的代码一般不适合特定的需求,需要根据实际需要进行二次开发。
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文件。使用方法如下:
- 在
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实体类所在包路径。
- 创建代码生成器,并配置相应参数,例如:
```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
为策略配置。创建了相应的配置后,调用AutoGenerator
的execute()
方法即可生成代码。
在以上代码中,setInclude("user")
表示只生成user
表对应的Java实体类、Mapper接口及其XML文件,也可不传入参数表示生成所有表的代码。
- 执行代码生成器即可:
```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 {@Autowiredprivate UserMapper userMapper;@Overridepublic 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 {@Autowiredprivate UserMapper userMapper;@Overridepublic 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.MybatisPlusInterceptorglobal-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";@Overridepublic Expression getTenantId(boolean where) {// 获取当前租户ID,可以从Session、ThreadLocal等获取String tenantId = TenantContext.getTenantId();// 如果没有获取到租户ID,则使用默认的租户IDif (tenantId == null || "".equals(tenantId.trim())) {tenantId = DEFAULT_TENANT_ID;}return new LongValue(tenantId);}@Overridepublic String getTenantIdColumn() {return "tenant_id";}@Overridepublic 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";@Overridepublic Expression getTenantId(boolean where) {// 获取当前租户ID,可以从Session、ThreadLocal等获取String tenantId = TenantContext.getTenantId();// 如果没有获取到租户ID,则使用默认的租户IDif (tenantId == null || "".equals(tenantId.trim())) {tenantId = DEFAULT_TENANT_ID;}return new LongValue(tenantId);}@Overridepublic String getTenantIdColumn() {return "tenant_id";}@Overridepublic 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)@OverrideList<User> selectList(@Param(Constants.WRAPPER) Wrapper<User> queryWrapper);// ...
}
以上代码中,@SqlParser(filter=true)
注解表示启用过滤器,对selectList()
方法进行租户ID过滤。
4. 最佳实践
4.1 掌握MyBatis数据表设计
在使用MyBatis进行开发之前,首先要掌握好数据表的设计。好的数据表设计可以极大地提高查询效率,降低系统复杂度。下面介绍一些数据表设计的要点。
4.1.1 表设计规范
- 表名命名规范:表名使用小写字母,单词之间用下划线隔开,例如
user_info
。 - 字段名命名规范:字段名同样使用小写字母,单词之间用下划线隔开,例如
user_id
。 - 主键命名规范:主键名统一使用
id
。 - 数据类型选取规范:MySQL支持多种数据类型,需要根据实际情况选择合适的数据类型。对于字符串类型,如果长度不确定,建议选择
VARCHAR(255)
,而不是TEXT
或者MEDIUMTEXT
。 - 字段注释规范:为每个字段添加注释,说明该字段的含义、取值范围等。
- 索引规范:为常用的查询字段创建索引,可以提高查询效率。但是过多的索引会降低写入性能。
4.1.2 表关系设计
在设计数据表时,需要考虑好表与表之间的关系,包括一对一、一对多和多对多等关系。下面介绍一些常见的表关系设计。
- 一对一关系:将主键作为外键,存放在从表中。例如,在一个订单系统中,一个订单只属于一个用户,一个用户只能拥有一个订单,这就是一对一关系。
- 一对多关系:将主键作为外键,存放在从表中。例如,在一个博客系统中,一个文章可以有多个评论,一个评论只能属于一个文章,这就是一对多关系。
- 多对多关系:需要使用中间表来维护关系,中间表存储两个表的主键,例如,在一个班级系统中,一个学生可以属于多个班级,一个班级可以拥有多个学生,这就是多对多关系。
4.2 映射文件编写的要点
MyBatis的映射文件用于定义SQL语句和映射规则,下面介绍一些映射文件编写的要点。
4.2.1 SQL语句编写
- 避免使用SELECT *:应该明确指出需要查询的字段,而不是查询全部字段。
- 注意参数类型:在使用Mapper接口调用SQL查询时,参数类型必须与Mapper接口方法参数类型一致或者符合JavaBean规范。
- 使用动态SQL:可以根据不同的条件排除或者包含某个SQL片段,以达到避免重复代码的作用。
- 使用别名:在多个表或者多个字段具有相同名称时,需要使用别名来消除歧义,提高可读性。
4.2.2 映射规则编写
- 映射关系标签:MyBatis提供了很多映射关系标签,例如
<resultMap>
、<association>
、<collection>
等。需要根据不同情况选择合适的映射关系标签。 - 映射ID命名:在定义映射ID时,应该使用规范的命名方式,例如
getUserById
、listUsers
等。 - 属性配置:可以为映射属性添加配置,例如添加
not-null="true"
表示该属性不能为空。 - 鉴别器:对于一些复杂的查询语句,需要使用鉴别器(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语句的一部分进行查询操作。虽然使用子查询可以实现较为复杂的查询操作,但是也会带来一些性能问题,主要表现在以下几个方面:
- 子查询会增加数据库的负载,导致查询速度变慢。
- 子查询可能需要重复执行多次,而每次执行都需要进行一次完整的查询操作,从而造成额外的开销。
- 子查询可能会引起死锁或者线程阻塞等问题,从而降低数据库的并发性能。
因此,在编写SQL语句时,应该尽可能避免使用子查询。可以通过以下几种方式来替代使用子查询的情况:
- 使用多表连接查询:使用多表连接查询可以避免使用子查询,通过将多个表的数据进行连接,完成一次完整的查询操作。
- 使用内置函数:数据库提供了许多内置函数,可以用于实现一些高级的查询操作,例如聚合函数、数学函数、字符串函数等。使用内置函数可以减少查询复杂度,避免使用子查询。
- 使用临时表:如果必须使用子查询,可以尝试使用临时表。先将子查询的结果存储到一个临时表中,然后在主查询操作中使用该临时表,可以避免子查询多次执行。
以下是一个使用多表连接查询替代子查询的示例:
-- 子查询
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 避免使用%前缀模糊查询
%前缀模糊查询指的是在查询字符串时,使用%作为前缀进行模糊匹配。虽然使用%前缀模糊查询可以实现快速查找匹配字符串的功能,但同时也会带来一些性能问题,主要表现在以下几方面:
- %前缀模糊查询会增加数据库的负载,导致查询速度变慢。
- %前缀模糊查询可能需要对整个表进行扫描,而这将是一个非常耗时的操作。
- %前缀模糊查询不利于索引的使用,因为%前缀模糊查询无法使用普通的B树索引。
因此,在编写SQL语句时,应该尽可能避免使用%前缀模糊查询。可以通过以下方式来替代使用%前缀模糊查询的情况:
- 使用全文索引:数据库提供了全文索引功能,可以用于实现全文搜索。使用全文索引可以取代%前缀模糊查询,提高查询效率。
- 使用后缀模糊查询:如果必须进行模糊查询,可以尝试使用后缀模糊查询。后缀模糊查询会优先使用索引,从而提高查询效率。
- 尽可能精确匹配:在编写查询条件时,应该尽可能精确匹配,避免使用过于宽泛的查询条件。
以下是一个使用全文索引替代%前缀模糊查询的示例:
-- %前缀模糊查询
SELECT * FROM articles WHERE title LIKE '%关键词%';-- 全文索引查询
SELECT * FROM articles WHERE MATCH(title) AGAINST('关键词' IN NATURAL LANGUAGE MODE);
5. 总结
5.1 MyBatis的未来
MyBatis作为一款优秀的ORM框架,其使用广泛,功能也非常强大。MyBatis的未来发展方向主要包括以下几个方面:
- 持续推进版本更新:MyBatis在不断地更新和迭代,新版本不仅修复了一些已知问题,还引入了一些新特性和优化,比如在3.x版本中引入了Spring Boot Starter,提供了更便捷的集成方式。
- 加强与其他组件的整合:MyBatis可以与Spring Framework等组件紧密配合,形成完善的应用架构。
- 多数据源支持:MyBatis在多数据源的支持上也越来越完善,可以针对不同的数据源进行不同的操作。
- 引入更多的插件:MyBatis插件是其扩展功能的重要手段,可以在SQL语句执行前、后进行一系列的自定义处理。未来MyBatis可能会开发更多的插件,以提高开发效率和使用体验。
5.2 MyBatis的注意事项以及常见问题
在使用MyBatis时,需要注意以下事项:
- 避免使用SELECT *:使用明确指出需要查询的字段,而不是查询全部字段,可以提高查询效率。
- 显式指定参数类型:在使用Mapper接口调用SQL查询时,参数类型必须与Mapper接口方法参数类型一致或者符合JavaBean规范。
- 使用动态SQL:可以根据不同的条件排除或者包含某个SQL片段,以达到避免重复代码的作用。
- 使用缓存:MyBatis支持多种缓存方式,可以提高查询效率。但是需要注意缓存更新策略,以免出现数据不一致问题。
- 配置文件管理:MyBatis的配置文件中包含了许多的配置信息,需要注意配置信息的管理和维护。
在使用MyBatis过程中,也存在一些常见问题,例如:
- 多表查询使用联合查询的性能问题。
- 分页查询时,使用游标或者取所有数据的性能问题。
- MyBatis缓存机制带来的数据不一致问题。
- SQL语句过于复杂,导致难以调试和维护。
- 数据库连接池的优化问题。