エンティティは、ビジネスロジックにおける実体を表現するための概念である。データベース設計において重要な役割を果たし、現実世界の対象を抽象化したものとして定義される。
エンティティの基本的な概念
エンティティは、独立して存在し得る実体を表現する。例えば、「従業員」「商品」「顧客」などが該当する。これは固有の属性を持ち、システム内で一意に識別可能な対象として扱われる。
データモデリングにおいて、エンティティは以下の特徴を持つ。
public class Employee {
// 一意に識別するためのID
private Long employeeId;
// 従業員の属性
private String name;
private String department;
private Date hireDate;
// 識別子を取得するためのメソッド
public Long getEmployeeId() {
return employeeId;
}
}
データベースとの関係性
エンティティはデータベースのテーブルと密接な関係を持つ。各エンティティはテーブルに対応し、エンティティの属性はテーブルのカラムとして表現される。この対応関係により、オブジェクト指向プログラミングとリレーショナルデータベースの橋渡しが実現される。
データベースとの関係性は以下のように表現される。
CREATE TABLE employees (
employee_id BIGINT PRIMARY KEY, -- エンティティの識別子
name VARCHAR(100), -- 従業員名
department VARCHAR(50), -- 部署
hire_date DATE -- 雇用日
);
オブジェクトとの違い
エンティティは一般的なオブジェクトとは異なる特徴を持つ。最も重要な違いは、エンティティが永続化を前提としている点である。通常のオブジェクトはメモリ上にのみ存在するが、エンティティはデータベースに保存され、アプリケーションの実行が終了しても存続する。
また、エンティティは必ず一意の識別子を持つ。これは、データベース上での一意性を保証するために不可欠な要素である。
@Entity
public class Product {
@Id // 識別子であることを示すアノテーション
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long productId; // データベース上で一意となる識別子
// 他の属性やメソッド
}
このように、エンティティはデータベースとアプリケーションを結ぶ重要な要素として機能する。
Javaにおけるエンティティの役割
Javaのエンティティは、オブジェクト指向プログラミングとデータベースの架け橋として機能する。JPA(Java Persistence API)を用いることで、Javaオブジェクトとデータベースレコードの相互変換を実現する。
エンティティクラスの特徴
エンティティクラスは、特別なアノテーションと規約に従って実装される。
@Entity // エンティティであることを示すアノテーション
@Table(name = "customers") // マッピング先のテーブル名を指定
public class Customer {
@Id // 主キーであることを示す
@GeneratedValue(strategy = GenerationType.IDENTITY) // 主キーの生成方法を指定
private Long id;
@Column(nullable = false, length = 100) // カラムの制約を設定
private String name;
// デフォルトコンストラクタ(JPAの要件)
public Customer() {}
}
エンティティクラスは必ずprotectedまたはpublicアクセス修飾子を持つデフォルトコンストラクタが必要である。JPAがリフレクションを使用してインスタンスを生成する際に、この可視性レベルが要求される。セキュリティの観点からprotectedの使用が推奨される。
@Entity
public class Customer {
@Id
private Long id;
// JPAの要件を満たすデフォルトコンストラクタ
protected Customer() {}
// ビジネスロジックで使用するコンストラクタ
public Customer(Long id) {
this.id = id;
}
}
アノテーションの使用方法
JPAアノテーションを使用することで、エンティティの振る舞いを詳細に制御できる。
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long orderId;
@Temporal(TemporalType.TIMESTAMP) // 日時型の指定
private Date orderDate;
@ManyToOne(fetch = FetchType.LAZY) // 関連エンティティの読み込み方法を指定
@JoinColumn(name = "customer_id") // 外部キーのカラム名を指定
private Customer customer;
}
データの永続化との関連性
エンティティは永続化コンテキストによって管理される。このコンテキストはエンティティの状態を追跡し、必要に応じてデータベースとの同期を行う。
@PersistenceContext
private EntityManager entityManager;
public void saveCustomer(Customer customer) {
// エンティティの永続化
entityManager.persist(customer);
// 永続化コンテキストの状態がデータベースに反映される
// トランザクションのコミット時に実際のSQLが実行される
}
エンティティの実装方法
実際のエンティティ実装にあたり、適切な設計と実装手順の理解が不可欠である。以下、具体的な実装方法について解説する。
エンティティクラスの作成手順
エンティティクラスの作成は、以下のような手順で進める。
@Entity
@Table(name = "products")
public class Product { // Serializableの実装は任意
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
@Size(min = 3, max = 100)
@Column(name = "product_name")
private String name;
protected Product() {}
public Product(String name) {
this.name = name;
}
}
フィールドの定義とアクセス方法
フィールドの定義には、適切なアクセス制御とカプセル化が求められる。
@Entity
public class Employee {
// プライベートフィールドの定義
@Column(name = "salary")
@Convert(converter = SalaryAttributeConverter.class)
private BigDecimal salary;
// 値の検証を含むセッター
public void setSalary(BigDecimal salary) {
if (salary.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("給与は0以上である必要があります");
}
this.salary = salary;
}
// イミュータブルな値を返すゲッター
public BigDecimal getSalary() {
return new BigDecimal(salary.toString());
}
}
// 暗号化処理を行うコンバーター
@Converter
public class SalaryAttributeConverter implements AttributeConverter<BigDecimal, String> {
@Override
public String convertToDatabaseColumn(BigDecimal attribute) {
// 暗号化処理の実装
return encryptValue(attribute);
}
@Override
public BigDecimal convertToEntityAttribute(String dbData) {
// 復号化処理の実装
return decryptValue(dbData);
}
// 暗号化メソッド
private String encryptValue(BigDecimal value) {
// 暗号化ロジックの実装
return null; // 実際の実装では適切な暗号化処理を行う
}
// 復号化メソッド
private BigDecimal decryptValue(String value) {
// 復号化ロジックの実装
return null; // 実際の実装では適切な復号化処理を行う
}
}
リレーションシップの設定
エンティティ間の関連性を適切に定義することで、オブジェクト間の関係性を表現する。
@Entity
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 一対多の関連を定義
@OneToMany(
mappedBy = "department", // 関連の所有者を指定
cascade = CascadeType.ALL, // 操作の伝播方法を設定
fetch = FetchType.LAZY // 遅延ロードを指定
)
private Set<Employee> employees = new HashSet<>();
// 関連の管理メソッド
public void addEmployee(Employee employee) {
employees.add(employee);
employee.setDepartment(this);
}
public void removeEmployee(Employee employee) {
employees.remove(employee);
employee.setDepartment(null);
}
}
エンティティの活用例
エンティティの実践的な活用方法について、具体的な実装例を交えて解説する。
基本的なCRUD操作
エンティティに対する基本的なデータベース操作は、EntityManagerを介して実行される。
@Stateless
public class UserService {
@PersistenceContext
private EntityManager em;
// Create操作
public User createUser(String name, String email) {
User user = new User(name, email);
em.persist(user); // エンティティの永続化
return user;
}
// Read操作
public User findUser(Long id) {
return em.find(User.class, id); // 主キーによる検索
}
// Update操作
public User updateUser(Long id, String newEmail) {
User user = em.find(User.class, id);
user.setEmail(newEmail); // 永続化コンテキストが変更を検知
return user;
}
// Delete操作
public void deleteUser(Long id) {
User user = em.find(User.class, id);
em.remove(user); // エンティティの削除
}
}
エンティティマッピングの実践
複雑なデータ構造をエンティティとして表現する際、適切なマッピング設定が重要となる。
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
// 列挙型のマッピング
@Enumerated(EnumType.STRING)
private OrderStatus status;
// 埋め込み可能なオブジェクトのマッピング
@Embedded
private Address shippingAddress;
// 要素コレクションのマッピング
@ElementCollection
@CollectionTable(
name = "order_items",
joinColumns = @JoinColumn(name = "order_id")
)
private Set<OrderItem> items = new HashSet<>();
}
トランザクション管理との連携
エンティティの操作は、トランザクション境界内で適切に管理される必要がある。
public class OrderService {
@PersistenceContext
private EntityManager em;
@Transactional(rollbackOn = StockException.class)
public Order placeOrder(Long userId, List<OrderItem> items) {
// トランザクション開始
User user = em.find(User.class, userId);
Order order = new Order(user);
for (OrderItem item : items) {
// 在庫チェックと更新
Product product = em.find(Product.class, item.getProductId());
if (!product.hasStock(item.getQuantity())) {
throw new StockException("在庫不足");
}
product.reduceStock(item.getQuantity());
order.addItem(item);
}
em.persist(order);
return order;
// トランザクション終了(コミットまたはロールバック)
}
}
以上。