Careeroza
Real World Example
ShopEase for Business
ShopEase now hosts stores for many small brands (like Shopify or Dukaan). Small brands share one schema with a tenant_id; products are soft deleted so old orders and invoices stay valid; a filter lets the support team see deleted products.
@MappedSuperclass
public abstract class TenantEntity {
@TenantId
@Column(name = "tenant_id", nullable = false, updatable = false)
private String tenantId;
}
@Entity
@Table(name = "products")
@SQLDelete(sql = "update products set deleted_at = current_timestamp where id = ? and version = ?")
@FilterDef(name = "hideDeleted")
@Filter(name = "hideDeleted", condition = "deleted_at is null")
public class Product extends TenantEntity {
@Id @GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
private String name;
private BigDecimal price;
private Instant deletedAt;
@Version private Long version;
}
@Aspect
@Component
@RequiredArgsConstructor
class HideDeletedAspect {
private final EntityManager em;
// every service call hides deleted products unless the caller is support staff
@Before("execution(* com.shopease..*Service.*(..))")
public void enable() {
boolean support = SecurityContextHolder.getContext().getAuthentication()
.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals("ROLE_SUPPORT"));
Session session = em.unwrap(Session.class);
if (!support && session.getEnabledFilter("hideDeleted") == null) session.enableFilter("hideDeleted");
}
}Safety Net in the Database
A single forgotten condition in a native query can leak another brand's data. Big SaaS teams add a second wall in the database. PostgreSQL row-level security does that:
ALTER TABLE products ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON products
USING (tenant_id = current_setting('app.tenant_id'));
-- the app runs SET app.tenant_id = 'brand-a' when it borrows a connectionTip: Write an automated test that logs in as tenant A and calls every list endpoint, asserting no tenant B rows come back. Run it on every build.