W poprzednim wpisie przedstawiłem sposób na redukcję kodu w encjach przy pomocy dziedziczenia i adnotacji @MappedSuperclass. Rozwiązanie to możemy również stosować aby tworzyć kod bardziej przenośny, który niewielkim kosztem można użyć w innych projektach.
Większość aplikacji webowych stosuje autoryzację opartą o role (ang. Role Based Access Control), w takich wypadkach mamy zazwyczaj encję User oraz Role, pierwszą odpowiedzialną za przetrzymywanie informacji o użytkowniku a druga nazwy ról. O ile role są obszarem stałym – zawsze mają nazwę – o tyle użytkownicy często mają różne wariacje i relacje. Ot choćby powiązanie konta użytkownika z firmą.
// Copyright (C) 2009 Code-House // All rights reserved package org.code_house.security.domain; import java.util.Calendar; import java.util.HashSet; import java.util.Set; import javax.persistence.Column; import javax.persistence.ManyToMany; import javax.persistence.MappedSuperclass; import javax.persistence.Temporal; import javax.persistence.TemporalType; import org.code_house.domain.BaseEntity; /** * Klasa reprezentująca użytkownika. Powinna ona zostać nadpisana w docelowym * systemie poprzez plik orm.xml. * * @author Łukasz Dywicki. */ @MappedSuperclass public class SecurityUser extends BaseEntity { /** * Hasło użytkownika. */ @Column private String password; /** * Login użytkownika. */ @Column private String username; /** * Konto wygasło. */ @Column @Temporal(TemporalType.TIMESTAMP) private Calendar expire; /** * Grupy do których przynależy użytkownik. */ @ManyToMany(mappedBy = "users") private Set<Role> roles; /** * Konto zablokowane np. po 3 nieudanych próbach logowania. */ @Column private boolean locked; /** * Konto wyłączone np. przez administratora. */ @Column private boolean enabled; // Dalej gettery i settery }
Rola ma niekompletną definicję relacji do klasy SecurityUser, którą należy uzupełnić o informacje na temat encji docelowej i definicji tabeli pośredniczącej.
// Copyright (C) 2009 Code-House // All rights reserved package org.code_house.security.domain; import javax.persistence.Entity; import javax.persistence.ManyToMany; import javax.persistence.Table; import org.code_house.domain.NamedEntity; /** * Klasa reprezentująca rolę. Może to być np ADMIN, USER bądź cokolwiek innego. * * @author Łukasz Dywicki luke@code-house.org */ @Entity @Table(name = "roles") public class Role extends NamedEntity { @ManyToMany @JoinTable(name = "users_roles", joinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id") ) private Set<SecurityUser> users; // gettery i settery }
Oto kawałek konfiguracji XML, która uzupełni metadane i pozwoli na uruchomienie kodu. Atrybut target-entity pozwala na określenie tabeli docelowej relacji podczas gdy element inverse-join-column uzupełnia definicję tabeli pośredniczącej.
<?xml version="1.0" encoding="UTF-8"?> <entity-mappings version="1.0" xmlns="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd"> <package>org.code_house.security.domain</package> <entity class="Role"> <attributes> <many-to-many name="users" target-entity="org.code_house.management.domain.User"> <join-table> <inverse-join-column name="user_id" referenced-column-name="id" /> </join-table> </many-to-many> </attributes> </entity> <entity class="Memento"> <attributes> <one-to-one name="user" target-entity="org.code_house.management.domain.User"> <join-column name="user_id" /> </one-to-one> </attributes> </entity> </entity-mappings>
Przykład ten można wykorzystać by uzupełniać relacje, których nie sposób odzwierciedlić w kodzie.