Спящий режим: используйте одну таблицу для двух разных классов

#java #hibernate #hibernate-mapping

Вопрос:

Я работаю над приложением, которое должно поддерживать Employees и Customers . У Customers кого есть Address , Employees у кого нет. Я не хочу создавать для них две отдельные таблицы в базе данных. В базе данных у меня есть a users , a roles и users_roles таблица, которая указывает, является ли пользователь Сотрудником или Клиентом.

Вот что у меня происходит:

 users
------
id
first_name
last_name
credentials_id (the username from `credentials`)
 
 credentials
------
username
password
 
 addresses
------
id
city
country
street
 
 users_addresses
------
user_id
address_id
 

И вот как выглядят отображения в режиме гибернации:

 @Entity
@Table(name = "users")
public class Customer {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private int id;

    @Embedded
    @AttributeOverrides({
            @AttributeOverride(name = "firstName", column = @Column(name = "first_name")),
            @AttributeOverride(name = "lastName", column = @Column(name = "last_name"))
    })
    private UserDetails userDetails;

    @ManyToOne
    @JoinTable(name = "customers_addresses",
            joinColumns = {@JoinColumn(name = "user_id")},
            inverseJoinColumns = {@JoinColumn(name = "address_id")})
    private Address address;
}
 
 @Entity
@Table(name = "users")
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private int id;

    @Embedded
    @AttributeOverrides({
            @AttributeOverride(name = "firstName", column = @Column(name = "first_name")),
            @AttributeOverride(name = "lastName", column = @Column(name = "last_name"))
    })
    private UserDetails userDetails;
}
 
 @Embeddable
public class UserDetails {
    private String firstName;
    private String lastName;

    @OneToOne
    @JoinColumn(name = "credentials_id")
    private UserCredentials credentials;
}
 

Все это работает нормально, но когда я запрашиваю сотрудников (у createQuery("from Employee") них есть address поле на них, то есть null . Кроме того, когда я запрашиваю клиентов (с createQuery("from Customers") , результат также включает сотрудников.

Есть какие-нибудь идеи?

Комментарии:

1. Employee у него нет поля address , вы ожидаете, что оно будет установлено?

2. Я ожидаю, что запуск createQuery("from Employee") не вернет объект с an address .

3. CreateQuery(«от сотрудника»).getResultList(); он вернет a List<Employee> . address нигде не появится. Я не уверен, что это ваше сомнение или что-то, что на самом деле с вами происходит.

Ответ №1:

У меня есть a users , a roles и users_roles таблица, которая указывает, является ли пользователь a Employee или a Customer .

Могут Employee ли s и Customer s поменяться ролями?

Если нет, то наиболее разумным решением было бы использовать наследование:

 @Entity
@Inheritance(SINGLE_TABLE)
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private int id;

    @Embedded
    private UserDetails userDetails;

}

@Entity
public class Customer extends User {

    @ManyToOne
    @JoinTable(name = "customers_addresses",
            joinColumns = {@JoinColumn(name = "user_id")},
            inverseJoinColumns = {@JoinColumn(name = "address_id")})
    private Address address;
}

@Entity
public class Employee extends User {}

 

При вышеуказанной настройке from Employees будут возвращены только сотрудники, from Customers — клиенты и from Users будут выбраны все User s. Недостатком, конечно, является избыточная информация в виде столбца-дискриминатора.

Ответ №2:

Предполагая, что вы не хотите использовать наследование, вам, вероятно, понадобится @Filter :

 @Entity
@Table(name="users")
@SecondaryTable(
    name = "user_roles",
    pkJoinColumns = @PrimaryKeyJoinColumn(name = "user_id")
)
@FilterDef(
    name="filterByRole",
    parameters = @ParamDef( name="roleId",type="int" )
)
@Filter(
    name="filterByRole",
    condition="{ur}.role_id = :roleId",
    aliases = @SqlFragmentAlias( alias = "ur", table= "user_roles")
)
class Employee {
}
 

Недостатком является то, что вам нужно включить его перед запросом:

 entityManager
    .unwrap( Session.class )
    .enableFilter( "filterByRole" )
    .setParameter( "roleid", employeeRoleId);

List<Employee> employees = entityManager
    .createQuery("from Employee", Employee.class)
    .getResultList();
 

См. раздел фильтры в документации по Hibernate ORM

Существует также @Where аннотация, но я не думаю, что она будет работать в вашем случае, если вы не сможете фильтровать, используя только столбцы в таблице users .

Комментарии:

1. «от сотрудника верните объект с адресом, потому что вы не указали класс сущности» — ну, не совсем так. Он возвращает объекты с адресами, потому что JPA не может знать, что объект с адресом не должен быть сотрудником. Это просто предполагает, что все содержимое таблицы представляет сотрудников, поскольку в отображении нет ничего, что указывало бы на обратное.

2. Что-то не так, когда Hibernate ORM преобразует форму запроса HQL в SQL, он добавит необходимые столбцы в SQL. У сотрудника нет столбца адреса, поэтому он не должен отображаться в предложении SQL select. Я не понимаю, зачем добавлять столбец адреса, поскольку это не поле сотрудника.

3. Я удалил этот комментарий о передаче сущности