List object mapped as EAGER is being fetched as LAZY - migrating Spring + Hibernate from 4 to 5 throwing org.hibernate.LazyInitializationException - jpa

Spring version: 5.3.19
Hibernate: 5.4.24.Final
The problem: When trying to get the List compartments from class CriticalFlight after ScrollableData gets Cleaned up #Cleanup, the list is empty since fetch was never executed.
Custom class ScrollableData execution snipped code:
List<ENTITY> filteredEntities;
#Cleanup ScrollableData<ENTITY> scrollableData =
getScrollableData(
filter,
myMarketChecker,
additionalFilters,
staticPredicateBuilders);
filteredEntities = scrollableData.getAll();
return filteredEntities;
I loop into the list returned and try to access List compartments
then got: "org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.pros.travel.services.oandd.optimizer.alerts.entity.CriticalFlight.compartments, could not initialize proxy - no Session
"
Classes
Embeddable Class: CriticalFlightKey
#Data
#Embeddable
public class CriticalFlightKey implements Serializable
{
#DTOMapping(CriticalFlightDTO.FIELD_FLIGHTDATE)
#Convert(converter = DateToLocalDateAttributeConverter.class)
#Column(name = "FLTDATE", nullable = false)
private LocalDate flightDate;
#DTOMapping(CriticalFlightDTO.FIELD_DIM_CRRCODE)
#Column(name = "CRRCODE", nullable = false)
private String carrierCode;
#DTOMapping(CriticalFlightDTO.FIELD_DIM_FLTNUM)
#Column(name = "FLTNUM", nullable = false)
private String flightNumber;
#DTOMapping(CriticalFlightDTO.FIELD_DIM_ORGN)
#Column(name = "ORGN", nullable = false)
private String origin;
#DTOMapping(CriticalFlightDTO.FIELD_DIM_DSTN)
#Column(name = "DSTN", nullable = false)
private String destination;
}
Parent Class: CriticalFlight
#Data
#EqualsAndHashCode(of = {"id"})
#Entity
#Table(name = "OD_CRITICAL_FLIGHTS")
public class CriticalFlight implements
{
#JsonUnwrapped
#EmbeddedId
#DTOMapped
public CriticalFlightKey id;
...
....
#JsonManagedReference
#OneToMany(fetch = FetchType.EAGER)
#Fetch(FetchMode.SUBSELECT)
#JoinColumns({
#JoinColumn(name="FLTDATE", referencedColumnName="FLTDATE"),
#JoinColumn(name="CRRCODE", referencedColumnName="CRRCODE"),
#JoinColumn(name="FLTNUM", referencedColumnName="FLTNUM"),
#JoinColumn(name="ORGN", referencedColumnName="ORGN"),
#JoinColumn(name="DSTN", referencedColumnName="DSTN")
})
private List<CriticalFlightCmp> compartments = new ArrayList<>();
}
Embeddable class for child: CriticalFlightCmpKey
#Data
#Embeddable
public class CriticalFlightCmpKey implements Serializable
{
#Convert(converter = DateToLocalDateAttributeConverter.class)
#Column(name = "FLTDATE", nullable = false)
private LocalDate flightDate;
#Column(name = "CRRCODE", nullable = false)
private String carrierCode;
#Column(name = "FLTNUM", nullable = false)
private String flightNumber;
#Column(name = "ORGN", nullable = false)
private String origin;
#Column(name = "DSTN", nullable = false)
private String destination;
#Column(name = "CMPCODE", nullable = false)
private String cmpCode;
}
Child Class: CriticalFlightCmp
#Data
#EqualsAndHashCode(of = {"id"})
#Entity
#Table(name = "OD_CRITICAL_FLIGHTS_CMP")
public class CriticalFlightCmp implements IPersistable<CriticalFlightCmpKey>
{
#EmbeddedId
private CriticalFlightCmpKey id;
..
...
}
Custom class ScrollableData which uses org.hibernate.ScrollableResults to execute the query
#Slf4j
public class ScrollableData<ENTITY extends IPersistable> implements Closeable
{
private static final int SCROLLABLE_FETCH_SIZE = 10000;
private final Class<ENTITY> entityClass;
private final ScrollableResults results;
private final EntityManager entityManager;
private final List<IScrollableFilter<ENTITY>> filters = new ArrayList<>();
public ScrollableData(
Class<ENTITY> entityClass,
ScrollableResults results,
EntityManager entityManager)
{
this.entityClass = entityClass;
this.results = results;
this.entityManager = entityManager;
}
/**
* Create scrollable data from a query and entity manager session.
*
* #param entityManager Entity manager from which the query was built from.
* #param query Query to scroll on.
* #return Scrollable data
*/
static <ENTITY extends IPersistable> ScrollableData<ENTITY> fromQuery(
Class<ENTITY> entityClass,
EntityManager entityManager,
CriteriaQuery query)
{
ScrollableResults results = entityManager.createQuery(query)
.unwrap(Query.class)
.setReadOnly(true)
.setFetchSize(SCROLLABLE_FETCH_SIZE)
.setCacheable(false)
.scroll(ScrollMode.FORWARD_ONLY);
return new ScrollableData<>(entityClass, results, entityManager);
}
public List<ENTITY> getAll()
{
List<ENTITY> allEntities = new ArrayList<>();
while (next())
{
allEntities.add(get());
}
return allEntities;
}
/**
* Clears the hibernate session of any entities it's caching.
*/
public void clearSession()
{
log.debug("Clearing Session for {}", entityClass.getSimpleName());
Session hibernateSession = entityManager.unwrap(Session.class);
hibernateSession.clear();
}
/**
* Closes the scrollable results and the session contained in the entity manager.
*/
public void close()
{
clearSession();
if (results != null)
{
log.debug("Closing ScrollableResults for {}",
entityClass.getSimpleName());
results.close();
}
if (entityManager != null)
{
log.debug("Clearing EntityManager for {}", entityClass.getSimpleName());
entityManager.close();
}
}
}

Related

JPA, nothing is added to Database when commit

I have two tables (command and commandLine), i want to write in both when i insert a new command
Here is my Command Object, I use a OneToMany to map with the object CommandLine
#Entity
#Table(name = "t_commands")
public class Command {
#Id #Getter
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idCommand;
#Getter #Setter
#ManyToOne(targetEntity = User.class)
#JoinColumn(name = "idUser", nullable = false)
private User user;
#Getter #Setter
#Column(name = "commandDate")
private String date;
#Getter #Setter
#OneToMany(targetEntity = CommandLine.class, mappedBy = "idCommand")
private List<CommandLine> lines = new ArrayList<>();
public Command(User user, String date) {
this.user = user;
this.date = date;
}
public Command() {
}
and my CommandLine object, I use ManyToOne to map with the object Command
#Entity
#Table(name = "t_commandlines")
public class CommandLine {
#Id #Getter
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idCommandLine;
#Getter
#ManyToOne(targetEntity = Command.class, cascade = CascadeType.ALL)
#JoinColumn(name = "idCommand", nullable = false)
private Command idCommand;
#Getter #Setter
#ManyToOne(targetEntity = Article.class)
#JoinColumn(name = "idArticle", nullable = false)
private Article article;
#Getter #Setter
private int quantity;
public CommandLine(Article article, int quantity) {
this.article = article;
this.quantity = quantity;
}
and my CommandDAO
public class CommandDAO implements IDao<Command> {
#Override
public boolean create(Command object) {
connect().persist(object);
return true;
}
}
I use interface
public interface IDao<T> {
default EntityManager connect() {
EntityManagerFactory entityManagerFactory;
EntityManager entityManager;
entityManagerFactory = Persistence.createEntityManagerFactory("webstore");
entityManager = entityManagerFactory.createEntityManager();
return entityManager;
}
default T read(Long id){return null;}
default List<T> getAll() {return null;}
default boolean create(T object) {return false;}
default boolean update(T object) {return false;}
default boolean delete(T object) {return false;}
default Long getCount() {return 1L;}
}
I have no error, but nothing is writing in my db, I have no problem to retrieve data from the db with this structure, but impossible to write
It's not applying the changes because there is no transaction and so the persist operation is not flushed to the database.
Here a simplified example of what the code should look like:
public class CommandDAO implements IDao<Command> {
#Override
public boolean create(Command object) {
EntityManager em = connect();
try {
em.getTransaction().begin();
em.persist(object);
em.getTransaction().commit();
return true;
}
finally {
em.close();
}
}
}
Also, there is no reason to create the EntityManagerFactory every time connect() is called. The creation of the factory is quite heavy but the factory is thread safe, you can reuse it when you need it.
It makes sense to create an EntityManager every time you need it, but you have to close it when you are done with it.

Jpa Auditing dont save data in table auditing

I have to implementes Auditing in my aplication.. i inserting this data correctly
but i want to save all atributter from my Entity ,
Exemple, name, epigrafe, .. and olthers.
I implemented the mothod but dosent work, just dont save the atributte..
lets see..
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "logradouros_historico", schema = "aud")
public class LogradourosHistorico {
#Id
#GeneratedValue
private Long id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "id_logradouro")
private Logradouros logradouro;
#CreatedBy
private String modificadoPor;
#CreatedDate
#Temporal(TemporalType.TIMESTAMP)
private Date modifiedDate = new Date();
#Enumerated(EnumType.STRING)
private Acoes acao;
#Column(name = "nome")
private String nome; //nome do logradouro
public LogradourosHistorico() {
super();
}
public LogradourosHistorico(Logradouros logradouro, String modificadoPor,
Acoes acao) {
super();
this.logradouro = logradouro;
this.modificadoPor = modificadoPor;
this.acao = acao;
}
//getters and setters
my class entityListner
public class LogradourosEntityListener {
#PostPersist
public void prePersist(Logradouros target) {
perform(target, Acoes.INSERTED);
}
#PreUpdate
public void preUpdate(Logradouros target) {
perform(target, Acoes.UPDATED);
}
#PreRemove
public void preRemove(Logradouros target) {
perform(target, Acoes.DELETED);
}
#Transactional()
private void perform(Logradouros target, Acoes acao) {
target.getNome();
EntityManager entityManager = BeanUtil.getBean(EntityManager.class);
entityManager.persist(new LogradourosHistorico(target, acao));
}
}
my class Logradouros
#Entity
#EntityListeners(LogradourosEntityListener.class)
#Table(name = "logradouros", schema = "glb", uniqueConstraints= #UniqueConstraint(columnNames={"id_entidade", "idLogradouro"}))
public class Logradouros extends Auditable<String> implements Serializable {
private static final long serialVersionUID = 3703309412387185484L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int idLogradouro;
#Column(name = "cep_geral")
private String cepGeral;
#Column(name = "epigrafe")
private String epigrafe;
#NotNull
#Column(name = "nome")
private String nome;
#Column(name = "nome_exibicao")
private String nomeExibicao;
#JoinColumn(name = "id_entidade")
#ManyToOne(/*cascade = CascadeType.ALL*/)
private Entidades entidade;
#NotNull
#JoinColumn(name = "id_municipio")
#ManyToOne(/*cascade = CascadeType.ALL*/)
private Municipios municipio;
// gettrs and settrs
so what i did wrong because i cant get the nome of entity Logradouros

JPA Entity Mappings between two tables

I keep getting the following error with my Entity mappings.
Caused by: org.hibernate.AnnotationException: mappedBy reference an unknown target entity property: edu.indstate.ics.transcript.web.dao.entity.Swrhxml.swbhxml in edu.indstate.ics.transcript.web.dao.entity.Swbhxml.swrhxmls
I am not sure what I am doing wrong. Could use some insight and help on what I am missing here.
My Entity classes are as follows:
#Entity
#Table(name = "SWBHXML" )
public class Swbhxml implements DatabaseObject, Serializable {
private List<Swrhxml> swrhxmls;
private static final long serialVersionUID = 1L;
private Long swbhxmlTransId;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "SWBHXML_TRANS_ID", nullable = false)
public Long getSwbhxmlTransId() {
return swbhxmlTransId;
}
public void setSwbhxmlTransId(Long swbhxmlTransId) {
this.swbhxmlTransId = swbhxmlTransId;
}
#OneToMany(mappedBy = "swbhxml", cascade = CascadeType.ALL)
public List<Swrhxml> getSwrhxmls() {
return swrhxmls;
}
public void setSwrhxmls(List<Swrhxml> swrhxmls) {
this.swrhxmls = swrhxmls;
}
}
#Entity
#Table(name = "SWRHXML" )
public class Swrhxml implements DatabaseObject, Serializable {
private Swbhxml swbhxml;
private static final long serialVersionUID = 1L;
private Long SwrhxmlTransId;
private String SwrhxmlHxpsCode;
private Date SwrhxmlTimeStamp;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name="SWBHXML_TRANS_ID")
public Swbhxml getSwrhxml() {
return swbhxml;
}
public void setSwrhxml(Swbhxml swbhxml) {
this.swbhxml = swbhxml;
}
#Column(name = "SWRHXML_HXPS_CODE", length = 15)
public String getSwrhxmlHxpsCode() {
return SwrhxmlHxpsCode;
}
public void setSwrhxmlHxpsCode(String SwrhxmlHxpsCode) {
this.SwrhxmlHxpsCode = SwrhxmlHxpsCode;
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "SWRHXML_TIMESTAMP", nullable = false)
#Temporal(TemporalType.TIMESTAMP)
public Date getSwrhxmlTimeStamp() {
return SwrhxmlTimeStamp;
}
public void setSwrhxmlTimeStamp(Date SwrhxmlTimeStamp) {
this.SwrhxmlTimeStamp = SwrhxmlTimeStamp;
}
}
You use
`mappedBy = "swbhxml"`
^
|___ b here
, but the annotated association is
Swbhxml getSwrhxml()
^
|___ r here
Your getter and setter are named incorrectly. And frankly, with such cryptic and close entity names, you'll probably have many such bugs.

Seeing "referencedColumnNames(ID) ... not mapped to a single property" error with a 1-M relationship after adding a composite key to the "1" side

I have an existing JPA entity ("Reference") with an ID column as its primary key that it inherits from a base class "BaseEntity" (using the #MappedSuperclass annotation on the superclass).
I also have a 1-M relationship between a Reference and another entity called Violation. Violation was previously defined with a foreign key "REFERENCE_ID" to the "ID" column of the Reference entity.
Recently, I tried to add an unrelated composite key to the Reference entity. This should not have affected the 1-M relationship between Reference and Violation. However, when I run the code in my tomcat server, I see the following stack trace:
Caused by: org.hibernate.AnnotationException: referencedColumnNames(ID) of org.qcri.copydetection.sdk.metastore.entity.Violation.reference referencing org.qcri.copydetection.sdk.metastore.entity.Reference not mapped to a single property
at org.hibernate.cfg.BinderHelper.createSyntheticPropertyReference(BinderHelper.java:205) ~[hibernate-annotations-3.5.6-Final.jar:3.5.6-Final]
at org.hibernate.cfg.ToOneFkSecondPass.doSecondPass(ToOneFkSecondPass.java:110) ~[hibernate-annotations-3.5.6-Final.jar:3.5.6-Final]
at org.hibernate.cfg.AnnotationConfiguration.processEndOfQueue(AnnotationConfiguration.java:541) ~[hibernate-annotations-3.5.6-Final.jar:3.5.6-Final]
at org.hibernate.cfg.AnnotationConfiguration.processFkSecondPassInOrder(AnnotationConfiguration.java:523) ~[hibernate-annotations-3.5.6-Final.jar:3.5.6-Final]
at org.hibernate.cfg.AnnotationConfiguration.secondPassCompile(AnnotationConfiguration.java:380) ~[hibernate-annotations-3.5.6-Final.jar:3.5.6-Final]
at org.hibernate.cfg.Configuration.buildMappings(Configuration.java:1206) ~[hibernate-core-3.5.6-Final.jar:3.5.6-Final]
at org.hibernate.ejb.Ejb3Configuration.buildMappings(Ejb3Configuration.java:1459) ~[hibernate-entitymanager-3.5.6-Final.jar:3.5.6-Final]
at org.hibernate.ejb.EventListenerConfigurator.configure(EventListenerConfigurator.java:193) ~[hibernate-entitymanager-3.5.6-Final.jar:3.5.6-Final]
at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:1086) ~[hibernate-entitymanager-3.5.6-Final.jar:3.5.6-Final]
at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:685) ~[hibernate-entitymanager-3.5.6-Final.jar:3.5.6-Final]
at org.hibernate.ejb.HibernatePersistence.createContainerEntityManagerFactory(HibernatePersistence.java:73) ~[hibernate-entitymanager-3.5.6-Final.jar:3.5.6-Final]
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:268) ~[spring-orm-3.1.2.RELEASE.jar:3.1.2.RELEASE]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:310) ~[spring-orm-3.1.2.RELEASE.jar:3.1.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1514) ~[spring-beans-3.1.2.RELEASE.jar:3.1.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1452) ~[spring-beans-3.1.2.RELEASE.jar:3.1.2.RELEASE]
... 39 common frames omitted
Here is the code for the 3 classes involved:
#Entity
#Table(name = "REFERENCE")
#XmlRootElement
#XmlAccessorType(XmlAccessType.PROPERTY)
#IdClass(Reference.ContextualName.class)
public class Reference extends BaseEntity {
#Column(name= "LOCATION", unique=true)
#XmlElement
private String location;
#Id
#AttributeOverrides({
#AttributeOverride(name = "name", column = #Column(name = "NAME")),
#AttributeOverride(name = "account", column = #Column(name = "ACCOUNT_ID"))
})
#Column(name = "NAME")
#XmlElement
private String name;
#ManyToOne(optional=false)
#XmlTransient
#JoinColumn(name = "ACCOUNT_ID", referencedColumnName = "ID")
private Account account;
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public Reference() {}
public Reference(String name) {
setName(name);
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public Account getAccount() {
return this.account;
}
public void setAccount(Account account) {
this.account = account;
}
#Embeddable
private class ContextualName implements Serializable {
private static final long serialVersionUID = -3687389984589209378L;
#Basic(optional = false)
#Column(name = "NAME")
#XmlElement
private String name;
#ManyToOne(optional=false)
#XmlTransient
#JoinColumn(name = "ACCOUNT_ID", referencedColumnName = "ID")
private Account account;
ContextualName() {}
}
}
#MappedSuperclass
#XmlAccessorType(XmlAccessType.FIELD)
public abstract class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
#XmlElement
private Long id;
#Basic(optional = true)
#Column(name = "CREATED", insertable = false, updatable = false, columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
#Temporal(TemporalType.TIMESTAMP)
#XmlElement
private Date creationDate;
protected BaseEntity() {}
public Long getId() {
return id;
}
public void setId(Long id) {
if(this.id==null) {
this.id = id;
} else if (this.id!=id) {
throw new IllegalArgumentException("Cannot change the id after it has been set, as it is a generated field.");
}
}
public Date getCreationDate() {
return creationDate;
}
public void setCreationDate(Date creationDate) {
if(this.creationDate==null) {
this.creationDate = creationDate;
} else if (this.creationDate!=creationDate) {
throw new IllegalArgumentException("Cannot change the creation-date after it has been set, as it is a generated field.");
}
}
}
#Entity
#Table(name = "VIOLATION")
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Violation extends BaseEntity {
#ManyToOne (optional=false, fetch= FetchType.EAGER)
#JoinColumn(name = "REFERENCE_ID", referencedColumnName = "ID")
private Reference reference;
#ManyToOne (optional=false, fetch= FetchType.EAGER)
#JoinColumn(name = "SUSPECT_ID", referencedColumnName = "ID")
private Suspect suspect;
#ManyToOne (optional=false, fetch= FetchType.EAGER)
#XmlTransient
#JoinColumn(name = "SEARCH_ID", referencedColumnName = "ID")
private Search search;
#Basic(optional = false)
#Column(name = "SCORE")
#XmlElement
private double score;
public Violation() {}
public Violation(Search search, Reference ref, Suspect sus, double score) {
this.search = search;
this.reference = ref;
this.suspect = sus;
this.score = score;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
public Reference getReference() {
return reference;
}
public void setReference(Reference reference) {
this.reference = reference;
}
public Suspect getSuspect() {
return suspect;
}
public void setSuspect(Suspect suspect) {
this.suspect = suspect;
}
public Search getSearch() {
return search;
}
public void setSearch(Search search) {
if(this.search!=null && this.search!=search) {
this.search.removeViolation(this);
}
this.search = search;
if(search!=null) {
if(!search.getViolations().contains(this)) {
search.addViolation(this);
}
}
}
}
To cut a long story short, I'm totally confused how to go about adding a composite key to an existing (legacy) entity that already has an ID column. I can't remove the ID column, nor can I change the 1-M relationship between Reference and Violation. I can't for the life of me understand the error message because the "REFERENCE_ID" foreign key column of the Violation entity is being mapped to a single "ID" column of the Reference entity.
Many thanks in advance!

How to correctly do a manytomany join table in JPA?

I need 3 entities: User, Contract (which are a many to many relation) and a middle entity: UserContract (this is needed to store some fields).
What I want to know is the correct way to define the relationships between these entities in JPA/EJB 3.0 so that the operations (persist, delete, etc) are OK.
For example, I want to create a User and its contracts and persist them in a easy way.
Currently what I have is this:
In User.java:
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<UserContract> userContract;
In Contract.java:
#OneToMany(mappedBy = "contract", fetch = FetchType.LAZY)
private Collection<UserContract> userContract;
And my UserContract.java:
#Entity
public class UserContract {
#EmbeddedId
private UserContractPK userContractPK;
#ManyToOne(optional = false)
private User user;
#ManyToOne(optional = false)
private Contract contract;
And my UserContractPK:
#Embeddable
public class UserContractPK implements Serializable {
#Column(nullable = false)
private long idContract;
#Column(nullable = false)
private String email;
Is this the best way to achieve my goals?
Everything looks right. My advice is to use #MappedSuperclass on top of #EmbeddedId:
#MappedSuperclass
public abstract class ModelBaseRelationship implements Serializable {
#Embeddable
public static class Id implements Serializable {
public Long entityId1;
public Long entityId2;
#Column(name = "ENTITY1_ID")
public Long getEntityId1() {
return entityId1;
}
#Column(name = "ENTITY2_ID")
public Long getEntityId2() {
return entityId2;
}
public Id() {
}
public Id(Long entityId1, Long entityId2) {
this.entityId1 = entityId1;
this.entityId2 = entityId2;
}
}
protected Id id = new Id();
#EmbeddedId
public Id getId() {
return id;
}
protected void setId(Id theId) {
id = theId;
}
}
I omitted obvious constructors/setters for readability. Then you can define UserContract as
#Entity
#AttributeOverrides( {
#AttributeOverride(name = "entityId1", column = #Column(name = "user_id")),
#AttributeOverride(name = "entityId2", column = #Column(name = "contract_id"))
})
public class UserContract extends ModelBaseRelationship {
That way you can share primary key implementation for other many-to-many join entities like UserContract.