第一次接触MyBatis Plus时,我被它的@TableName注解惊艳到了。记得当时接手一个老项目,数据库里有几十张表名带前缀"tbl_"的表,而Java实体类却都是干净的业务名称。要是用原生MyBatis,我可能得写几百行XML配置,但@TableName让我只用10分钟就解决了所有映射问题。
这个注解最基础的功能就是建立实体类与数据库表的对应关系。比如我们有个用户表叫"sys_user",但实体类命名为User。传统方式需要在每个Mapper XML里写resultMap,而用@TableName只需要这样:
java复制@TableName("sys_user")
public class User {
private Long id;
private String username;
// 其他字段...
}
value属性是最常用的配置项,它直接指定表名。但实际项目中我们经常会遇到更复杂的情况:
我曾在一个电商项目中遇到最棘手的情况是:商品表在不同环境中有不同命名(dev环境下叫"dev_product",prod环境下叫"t_product")。通过结合Spring的Profile和@TableName,我们优雅地解决了这个问题:
java复制@TableName(
value = "${table.name.product}",
keepGlobalPrefix = true
)
public class Product {
//...
}
然后在不同环境的配置文件中分别设置:
properties复制# dev环境
table.name.product=dev_product
# prod环境
table.name.product=t_product
去年做银行项目时,需要同时连接Oracle和MySQL数据库。Oracle有严格的schema概念,而MySQL的schema其实就是数据库名。@TableName的schema属性在这里派上了大用场。
schema属性的典型用法是这样的:
java复制// Oracle环境配置
@TableName(value = "ACCOUNT", schema = "FINANCE")
public class Account {
//...
}
// MySQL环境配置
@TableName(value = "account", schema = "finance_db")
public class Account {
//...
}
这里有个实际踩过的坑:在Oracle中,schema名称默认会转大写,而MySQL则保持原样。我们团队为此统一了规范:所有schema和表名在代码中都用小写,通过配置控制数据库的实际大小写:
yaml复制mybatis-plus:
configuration:
map-underscore-to-camel-case: true
capital-mode: false # 关闭自动转大写
对于需要动态切换schema的场景(比如SaaS系统),我推荐使用ThreadLocal配合自定义注解。下面是我们项目中实际使用的方案:
java复制@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@TableName(schema = "#{@schemaProvider.getCurrentSchema()}")
public @interface DynamicSchema {
}
// 使用示例
@DynamicSchema
public class TenantEntity {
//...
}
配套的SchemaProvider实现:
java复制@Component
public class SchemaProvider {
private static final ThreadLocal<String> SCHEMA_HOLDER = new ThreadLocal<>();
public String getCurrentSchema() {
return SCHEMA_HOLDER.get();
}
public static void setSchema(String schema) {
SCHEMA_HOLDER.set(schema);
}
}
在复杂项目里,数据库字段和实体属性往往不是简单的一一对应。比如我们可能遇到:
resultMap属性允许我们直接指定XML中定义的resultMap。举个例子:
xml复制<!-- mapper.xml -->
<resultMap id="userDetailMap" type="User">
<id column="uid" property="id"/>
<result column="user_name" property="username"/>
<result column="dept_info" property="department"
typeHandler="com.example.JsonTypeHandler"/>
</resultMap>
然后在实体类上引用:
java复制@TableName(resultMap = "userDetailMap")
public class User {
private Long id;
private String username;
private Department department;
//...
}
autoResultMap是MyBatis Plus提供的便利功能,默认false。当设置为true时,框架会自动构建resultMap。它的工作原理是:
我做过性能测试:在100个字段的宽表上,autoResultMap比手动编写resultMap的查询性能差异可以忽略不计(<1%)。但它有个局限:无法处理复杂的类型转换,这时候还是需要手动定义resultMap。
实际项目中我的经验法则是:
MyBatis Plus提供了一系列全局配置项,与@TableName注解配合使用能发挥更大威力。最常用的是table-prefix配置:
yaml复制mybatis-plus:
global-config:
db-config:
table-prefix: t_
schema: public
这样配置后,所有实体类默认会加上"t_"前缀。但有时候我们需要特例,比如用户表实际叫"sys_user"而不是"t_user"。这时候可以用:
java复制@TableName(value = "sys_user", keepGlobalPrefix = false)
public class User {
//...
}
keepGlobalPrefix属性控制是否保留全局前缀,默认false。我建议保持默认值,只在确实需要覆盖全局配置时才设置为false。
另一个实用技巧是结合@TableName和@TableId、@TableField实现完整映射:
java复制@TableName(value = "employee", schema = "hr")
public class Employee {
@TableId(value = "emp_no", type = IdType.ASSIGN_ID)
private Long id;
@TableField("emp_name")
private String name;
@TableField(value = "join_date", jdbcType = JdbcType.DATE)
private LocalDate joinDate;
}
这种声明式配置比XML更简洁,而且支持编译时检查。我在团队中推行这种风格后,Mapper XML文件减少了70%以上。
对于多租户系统,我们还可以结合TenantLineInnerInterceptor实现自动添加租户前缀:
java复制@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(
new TenantLineHandler() {
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
@Override
public Expression getTenantId() {
return new StringValue("tenant1");
}
@Override
public boolean ignoreTable(String tableName) {
return !tableName.startsWith("tenant_");
}
}
));
return interceptor;
}
这样配置后,所有以"tenant_"开头的表都会自动加上租户条件,而@TableName的value只需要写业务表名即可。