项目问题随笔(2)

        在项目中创建了用户表、租户表和房东表,并且租户和房东继承了用户实体类。用户表定义了公共属性,租户和房东只定义了各自的特有属性。为租户和房东创建了三层架构(Controller、Service、Mapper),用户则只创建了Mapper层。用户Mapper层定义了公共方法(如修改用户信息),租户和房东的Mapper层则定义了对自身特有属性的方法(如删除)。

现在我需要修改某租户的信息,操作如下:

  1. 在租户的Service层调用用户Mapper层修改公共属性信息。

  2. 在租户的Service层调用租户Mapper层修改特有属性信息。

        传递的参数是租户对象,它继承了用户。第一步操作正常,但第二步出现问题:租户对象包含公共属性和特有属性,而租户表只包含特有属性。我该如何解决租户对象继承导致的属性传递问题?

        另外,如果我有类似租户这样的多个对象(如房东、管理员),是否意味着我需要分别为它们创建包含特有属性的实体类?这样是否会增加资源占用和性能开销?


解决方案

1. 解决租户对象继承导致的属性传递问题

以下是几种解决方案:

方案1:使用DTO进行数据传递

// 在Service层
public void updateTenant(Tenant tenant) {
    // 1. 更新用户公共属性
    userMapper.updateUser(tenant); // 这里tenant作为User父类使用
    
    // 2. 创建只包含租户特有属性的DTO
    TenantSpecificDTO tenantSpecific = new TenantSpecificDTO();
    tenantSpecific.setId(tenant.getId());
    tenantSpecific.setTenantSpecificField1(tenant.getTenantSpecificField1());
    // 设置其他特有属性...
    
    // 更新租户特有属性
    tenantMapper.updateTenantSpecific(tenantSpecific);
}

方案2:在Mapper中使用动态SQL

<!-- 租户Mapper.xml -->
<update id="updateTenantSpecific" parameterType="Tenant">
    UPDATE tenant_table
    <set>
        <if test="tenantSpecificField1 != null">field1=#{tenantSpecificField1},</if>
        <if test="tenantSpecificField2 != null">field2=#{tenantSpecificField2},</if>
        <!-- 其他特有属性 -->
    </set>
    WHERE user_id=#{id}
</update>

方案3:使用Map传递参数

java

复制

// Service层
public void updateTenant(Tenant tenant) {
    userMapper.updateUser(tenant);
    
    Map<String, Object> params = new HashMap<>();
    params.put("id", tenant.getId());
    params.put("field1", tenant.getTenantSpecificField1());
    // 其他特有属性
    
    tenantMapper.updateTenantSpecific(params);
}

方案4:使用注解过滤属性 如果你的ORM框架支持(如MyBatis的@Param注解):

java

复制

@Update("UPDATE tenant_table SET field1=#{tenant.field1}, field2=#{tenant.field2} WHERE user_id=#{tenant.id}")
void updateTenantSpecific(@Param("tenant") Tenant tenant);

最佳实践建议

  • 推荐方案1(DTO):虽然需要额外创建类,但结构最清晰,职责最明确。

  • 保持一致性:整个项目中统一使用一种方案。

  • 考虑使用MapStruct:如果需要频繁转换,可以使用MapStruct等映射工具简化DTO转换。


2. 关于是否需要为每个角色创建独立实体类

是的,根据架构设计,为每个具有特有属性的角色(如房东、管理员等)创建包含其特有属性的实体类是一个合理且推荐的做法。这种设计模式符合面向对象的原则,尤其是继承和单一职责原则。

为什么需要为每个角色创建独立实体类?

  • 类型安全:每个角色的业务逻辑不同(如租户有“押金记录”,房东有“房产列表”),独立的类可以避免属性混淆。

  • 数据隔离:数据库表中,tenant表、landlord表、admin表通常只存储特有字段,实体类与表结构应一一对应。

  • 可维护性:修改某个角色的属性时,不会意外影响其他角色。

推荐实现方式

  • 继承 + 独立Mapper(你当前的结构)

    // 父类
    public class User {
        private Long id;
        private String name;
        private String email;
        // 公共属性...
    }
    
    // 子类:租户
    public class Tenant extends User {
        private BigDecimal deposit;  // 押金
        private LocalDate leaseEnd;  // 租约到期日
        // 租户特有属性...
    }
    
    // 子类:房东
    public class Landlord extends User {
        private List<Property> ownedProperties; // 拥有的房产列表
        private String bankAccount;             // 收款账号
        // 房东特有属性...
    }
  • 组合替代继承(更灵活) 如果角色可能重叠(如用户既是租户又是房东),考虑用组合:

    public class User {
        private Long id;
        private String name;
        // 公共属性...
        private TenantProfile tenantProfile;  // 租户资料(可为null)
        private LandlordProfile landlordProfile; // 房东资料(可为null)
    }

3. 关于资源占用和性能的考量

在实际企业开发中,这种设计模式确实常见,但也需要根据具体场景权衡利弊。以下是企业级实践中的真实考量和优化方案:

企业中的实际做法

  1. 主流方案:角色独立实体类(你的设计)

    • 适用场景:角色差异大、业务逻辑分离明确(如租户/房东/管理员的核心功能完全不同)。

    • 典型案例

      • 电商系统:UserCustomer(买家)/Merchant(卖家)/Admin

      • SaaS平台:AccountFreeUser/PaidUser/EnterpriseUser

  2. 变通方案:单表+角色字段

    • 适用场景:角色属性差异小、查询性能要求高。

    • 实现方式

      sql

      复制

      CREATE TABLE `user` (
        `id` BIGINT PRIMARY KEY,
        `name` VARCHAR(255),
        `type` ENUM('TENANT','LANDLORD','ADMIN'), -- 角色标识
        `deposit` DECIMAL(10,2),  -- 租户特有(type=TENANT时有效)
        `bank_account` VARCHAR(50) -- 房东特有(type=LANDLORD时有效)
      );
    • 优点:避免联表查询,性能更高。

    • 缺点:字段冗余(非当前角色的字段为NULL),需在代码中校验type

  3. 混合方案:公共表+扩展表

    • 设计示例

      -- 公共表(所有角色共用)
      CREATE TABLE `user` (
        `id` BIGINT PRIMARY KEY,
        `name` VARCHAR(255),
        `role_type` VARCHAR(20)
      );
      
      -- 租户扩展表
      CREATE TABLE `user_tenant` (
        `user_id` BIGINT PRIMARY KEY,
        `deposit` DECIMAL(10,2),
        FOREIGN KEY (`user_id`) REFERENCES `user`(`id`)
      );
      
      -- 房东扩展表
      CREATE TABLE `user_landlord` (
        `user_id` BIGINT PRIMARY KEY,
        `bank_account` VARCHAR(50),
        FOREIGN KEY (`user_id`) REFERENCES `user`(`id`)
      );
    • 企业应用:阿里云账号体系(基础信息表+企业/个人扩展表)。


关于资源占用和性能的真相

  1. 类增多是否影响性能?

    • JVM层面:增加的类会占用元空间(Metaspace),但现代JVM对类加载的优化极好,除非有数万个类(微服务架构下才需考虑)。

    • 实际影响:类数量对内存的占用远小于数据对象实例(如查询返回的List<User>)。

  2. 真正需要关注的性能瓶颈

    • 数据库查询:联表查询(JOIN)比单表查询慢,但可通过以下方式优化:

      • 索引优化:确保user_id和外键字段有索引。

      • 懒加载:MyBatis中配置<association fetchType="lazy">

      • 缓存:对公共数据(如user.name)用Redis缓存。

  3. 企业级优化技巧

    • 动态模型:复杂系统可用Map<String, Object>传递数据(如阿里TDDL),但牺牲类型安全。

    • 代码生成:若角色类型固定,用代码生成器自动创建Entity/Mapper(如MyBatis Generator)。

    • CQRS模式:读写分离,查询时直接返回DTO,绕过实体类转换。


决策建议

  • 选择继承设计(你的方案)

    • 角色功能差异显著(如租户有“交租金”方法,房东有“收租金”方法)。

    • 未来可能为不同角色添加独立业务逻辑。

    • 团队熟悉OOP设计模式。

  • 选择单表设计

    • 角色属性差异小(<5个特有字段)。

    • 需要极简查询(如用户登录无需区分角色)。

    • 项目初期快速迭代。


典型案例参考

  1. AWS IAM(身份访问管理)

    • 设计UserAdmin/Developer/Auditor独立实体。

    • 优化:策略模式(Policy Pattern)动态控制权限,避免if-else判断角色。

  2. 微信账号体系

    • 设计:单表存储基础信息,扩展字段用JSON存储(如ext_info字段存租户/房东特有属性)。

    • 优化:高频访问字段放主表,低频字段分离。


总结 在企业开发中,“类的数量”不是关键问题,而应关注:

  • 业务语义是否清晰(代码是否真实反映业务逻辑)。

  • 扩展性(新增角色是否要改原有代码)。

  • 性能关键路径(80%优化应投入在20%的热点代码上)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值