In my application, I have a several audited entity classes for example the following.
It contains multiple HAS-IS relations to other entities with various hibernate annotations.
#Entity
#Audited
public class Entity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private Integer Id;
#ManyToMany
private Set < Country> affectedCountries;
#OneToOne
private Impact impact;
#Enumerated(EnumType.STRING)
private Owner owner;
...
}
I am analyzing the the audit-trail with the following code snipplet,
which return all the attribute values from the audit table entity.
public List< AuditTrailForm> getAuditTrailEntries(Class< ?> clazz, Serializable id) {
AuditReader reader = AuditReaderFactory.get(this.getSession());
List < Number> revNumbers = reader.getRevisions(clazz, id);
List< AuditTrailForm> forms = new ArrayList();
Iterator< Number> it = revNumbers.iterator();
while(it.hasNext()) {
Number item = it.next();
Object obj = reader.find(clazz, id, item);
AuditInfo revision = reader.findRevision(AuditInfo.class, item);
BeanMap beanMap = new BeanMap(obj);
HashMap map = new HashMap();
Set keys = beanMap.keySet( );
Iterator keyIterator = keys.iterator( );
while( keyIterator.hasNext( ) ) {
String propertyName = (String) keyIterator.next( );
if (beanMap.getType(propertyName).equals(String.class)) {
String propertyValue = (String) beanMap.get( propertyName );
map.put(propertyName, propertyValue);
}
}
Date createdAt = revision.getTimestamp();
String user = revision.getUser();
AuditTrailForm form = new AuditTrailForm(user, createdAt, map);
forms.add(form);
}
return forms;
}
Works fine, however this doesn't take into account the traversing the relations in the class. Could I somehow develop a recursive algorithm, which would detect the type of the object attribute and then do the recursive call? Is there perhaps a better way to do this altogether?
A simpler approach...
I'm assuming that the saves to the object tree happen in a transaction. You could modify the audit tables to also add some unique identifier for each transaction.
That way you would be able to browse the audit history as a collection of change sets - e.g. see all changes made in a single transaction.
Not quite what you are asking for but would be a lot easier to achieve and give many of the benefits. What you are asking for would be very complicated to implement.
Related
I got next database structure with OneToOne relation:
[company]
company_id (PK)
company_name
[company_configuration]
company_configuration_id (Autoincrement, PK)
company_id (UNIQUE KEY,FK)
company_configuration_v
I have been using ORMlite and I have next classes for this two tables:
#DatabaseTable(tableName = "company")
public class Company {
public static final String ID_COMPANY = "company_id";
public static final String COMPANY_NAME = "company_name";
#DatabaseField(generatedId = true, columnName = ID_COMPANY)
private int idCompany;
#DatabaseField(columnName = COMPANY_NAME)
private String companyName;
#DatabaseTable(tableName = "company_configuration")
public class CompanyConfiguration {
public static final String COMPANY_CONFIGURATION_ID = "company_configuration_id";
public static final String COMPANY_ID = "company_id";
public static final String COMPANY_CONFIGURATION_V = "company_configuration_v";
#DatabaseField(generatedId = true, columnName = COMPANY_CONFIGURATION_ID)
private int idCompanyConfiguration;
#DatabaseField(foreign = true,foreignAutoRefresh = true, columnName = COMPANY_ID)
private Company companyId;
#DatabaseField(columnName = COMPANY_CONFIGURATION_V)
private String companyConfigurationV;
Here is OneToOne relation because I want to divide a table with many columns.
As you can see in the example above, there is not relation from Company class to CompanyConfiguration class.
I know that I can add this snippet of code(examle below) into Company class, but I don't need a #ForeignCollectionField becaues the collection will contain only one CompanyConfiguration object:
#ForeignCollectionField()
private ForeignCollection<CompanyConfiguration> companyConfigurations;
I need to add something like this (examle below) into Company class and will get the reference from Company class to CompanyConfiguration class:
#OneToOne(targetEntity = CompanyDbConfig.class)
#JoinTable(name = "company_configuration")
#JoinColumn(name = "id_company")
CompanyConfiguration companyConfiguration;
Shortly, I want to get Company object using ORMlite. See the example below. After fetching company from the database, I want to have and CompanyConfiguration object within company object.
Company company = daoCompany.queryForId(id); //daoCompany is an instance of ORMlite Dao class
Is it possible and how to do that using ORMlite?
I posted an OrmLite question myself so I looked through the unanswered questions to see if there was anything I could answer. Even though this is an old topic, I wanted to take a stab at it in case it could help someone.
I've read your post a few times and I think you're asking how to load the information from two tables into one model. You're separating a rather large table into two in the database but you want it to come back as one model. If that is correct, here's my take on the code. This assumes you want to use objects to build the query instead of passing in a query string.
public class CompanyResult
{
public long CompanyId { get; set; }
public long ConfigurationId { get; set; }
public string Name { get; set; }
public string ConfigurationV { get; set; }
}
var query = _db.From<Company>
.Join<CompanyConfiguration>((c, cc) => c.idCompany == cc.idCompany)
.Where(c => c.idCompany == companyId)
.Select<CompanyConfiguration>((c, cc) = new {
CompanyId = c.idCompany,
ConfigurationId = cc.idCompanyConfiguration,
Name = c.companyName,
ConfigurationV - cc.companyConfigurationV
});
var results = _db.Single<CompanyResult>(query);
You'd keep your existing models so they could be used as DTOs. You'd just be using the new model model above to pass back the exact properties you want.
*I wrote this in Notepad++, forgive any typos.
I have the following:
#Entity
#NamedQuery(name = "listCarsBySecurity", query = "SELECT c FROM Car c WHERE c.security = :security"
public class Car {
#Id
#GeneratedValue
private Long id;
#NotNull()
#Column(nullable = false)
private String make;
#NotNull()
#Column(nullable = false)
private String model;
// Some more fields
#NotNull()
#OneToOne (fetch = FetchType.LAZY, orphanRemoval=true)
private Security security = new Security();
// Some getters and setters
As you can see, the Car class has a "Security" object which is LAZY fetched. The security class looks like:
#Entity
public class Security {
#Id #GeneratedValue
private Long id;
// Security equipment. Add in alphanumerical order
private boolean abs;
private boolean airbag;
private boolean antispin;
// Some getters and setters
as you can see, the named query list try to list all cars which has a security entity equal to a provided security object.
The persistence method looks like:
#Stateless
public class CarEJB {
#PersistenceContext(unitName = "carcmsPU")
private EntityManager em;
public List<Car> listCarsBySecurity(Security security) {
TypedQuery<Car> query = em.createNamedQuery("listCarsBySecurity", Car.class);
query.setParameter("security", security);
return query.getResultList();
}
And a junit test looks like:
#Test
public void searchCar() throws Exception {
// Looks up the EJBs
carEJB = (CarEJB) ctx.lookup("java:global/classes/CarEJB");
// Create a new Ferrari with security = ABS brakes and Airbag
Car car = new Car();
car.setMake("Ferrari");
car.setModel("Some model");
car.setSubModel("Some sub model");
car.setEngine("Some engine");
car.setYear(1999);
car.getFuel().setGasoline(true);
car.setGearbox(Gearbox.AUTOMATIC);
car.setKilometres(323);
car.setDescription("This is a description");
Security security = new Security();
security.setAbs(true);
security.setAirbag(true);
car.setSecurity(security);
carEJB.createCar(car); // Persist
// Create a new security object and match it to the former one
Security securityObject = new Security();
securityObject.setAbs(true);
securityObject.setAirbag(true);
List<Car> carList = carEJB.listCarsBySecurity(securityObject);
assertTrue("Should contain at least 1 car with ABS and Airbag", carList.size() > 0 );
for (Car carTemporary : carList) {
System.out.println(carTemporary.toString());
}
}
The thing is that the list does not contain any cars at all. And I think I know why; the named query does try to match the security_id with NULL (since I have not define it).
My question is: How can I perform a query by passing a object as a query parameter with no ID and by not specify all fields which shall be compared inside that object? (or how exclude the ID from a search)?
Best regards
You can define a named query using OR and passing each one of the object's attributes. You can also use Criteria API to build a query based on the fields you want to query about. Since you already have a named query I'll leave that one to you.
If you decide to go that way (tough field by field comparation is kind of insane if your entity has way too many attributes). Using criteria you can do something like this:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Car> query = builder.createQuery(Car.class);
Root<Car> queryRoot = query.from(Car.class);
query.select(queryRoot);
Path<String> pathToYourField = root.get(yourField); //yourField is a variable containing the field.
//You can store all the variables in a list, iterate
//over them and do this for each one.
query.where(builder.and(builder.equal(pathToYourField, "particularValue"))); //You compare the path against a value.
//Rest of the fields / paths
TypedQuery<Car> typedQuery = entityManager.createQuery(query);
List<Car> cars = typedQuery.getResultList();
EDIT: About performance, check this links:
JPA Criteria vs NamedQueries
Another answer regarding Criteria vs HQL
Criteria overhead discussion
Lets say we have User entity class. User can be friends with other users. How can i map this self-reference collection field without creating a new entity called Connection or creating multiple entries in the database?
#Entity
public class User {
...
#ManyToMany
private Collection<User> friends;
...
}
USER_ID-FRIEND_ID
1 - 2
2 - 1 (duplicate... I don't need it)
Following is snapshot from my code for ElementEntity:
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
private List<ElementEntity> children;
#JoinColumn(name = "ParentId", referencedColumnName = "ElementId")
#ManyToOne(fetch = FetchType.LAZY)
private ElementEntity parent;
Where on database there are fields:
ElementId - primary key;
ParentId relation with parent
You can't - you need both records in the database.
Actually, for friendship relations, I'd say that a graph database like neo4j is the proper thing to use. There you have the two users and simply add an edge "friends".
At least you will need a relational table.
So you have a USER table and a FRIENDS:
user_id friend_id
1 2
But #Bozho answer is way better than mine (neo4j).
Well, in fact you can.
You can use annotations like #PreUpdate, #PrePersists, #PostUpdate and so to convert manually the elements of a collection. This way your entity can render then them way you want while in database you only store a raw text.
A more pausible alternative will be to use #Convert annotation, available since jpa 2.1 (#UserType in hibernate). It tells jpa to convert the field into another type everytime it read/save in database.
For it you should use #Convert anotation, specifying and AttributeConverter object.
For example
public class Parent {
#Id
private Integer id;
#Convert(converter = FriendConverter.class)
private Set<Parent>friends;
}
And converter class like the following:
#Component
public class FriendConverter implements AttributeConverter<List, String>{
#Autowired
private SomeRepository someRepository;
#Override
public String convertToDatabaseColumn(List attribute) {
StringBuilder sb = new StringBuilder();
for (Object object : attribute) {
Parent parent = (parent) object;
sb.append(parent.getId()).append(".");
}
return sb.toString();
}
#Override
public List convertToEntityAttribute(String dbData) {
String[] split = dbData.split(".");
List<Parent>friends = new ArrayList<>();
for (String string : split) {
Parent parent = someRepository.findById(Integer.valueOf(string));
friends.add(accion);
}
return friends;
}
}
It is a dummy implementation but it gives you the idea.
As a personal comment, I do recommend to map the relationship as it should. In the future it will avoid you problems. AttributeConverter comes in handy when working with enums
I have such entity:
#Entity
public class Album {
private Integer id;
private Integer ownerId;
private String name;
private String description;
private Date created;
#OneToMany #JoinColumn(name = "albumId")
private Set<AlbumUser> users = new HashSet<AlbumUser>();
#OneToMany #JoinColumn(name = "albumId")
private Set<Picture> pictures = new HashSet<Picture>();
}
and another one:
#Entity
public class Picture {
private Integer id;
private Integer creatorId;
private Integer albumId;
private Date created;
private String title;
private String description;
#ManyToOne #JoinColumn(name = "eventId")
private Event event;
}
Using Criteria API I want to get unique AlbumDs with filtered set of Picturs. I try something like this:
public Album read(Integer albumId, Set<Integer> picFilter) {
Criteria crit = getCurrentSession().createCriteria(Album.class, "album");
crit.add(Restrictions.idEq(albumId));
if (picFilter != null && !picFilter.isEmpty()) {
crit = crit.createAlias("album.pictures", "picture");
crit.add(Restrictions.in("picture.event.id", picFilter));
crit.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
}
Album resultDs = (Album) crit.uniqueResult();
return resultDs;
}
And here I get Album with all pictures associated. They're not filtered at all.
When I try to execute query printed by a logger, I get only four rows wich is the number of pictures with given eventId, but in the Album I get all pictures.
I also tried other ResultTransformers, but eventualy got many result (4) not distinct one.
What do I miss or do wrong?
You can not filter the content of Collections associated with an entity by including Restrictions on the Collection in the query. The query will only fetch the Albums. The content of the Collection can be fetched later, when the Collection is accessed. All you do is filter the Albums to retrieve only those Albums that contain the Pictures with the event ids.
If the Collection would only contain the Pictures that match your Criteria and you would get a partial Collection it would cause problems on updates, because Hibernate then think the filtered items have been removed and would update the database to reflect that change, actually removing the items from the Collection.
If you want to receive only some items from a Collection you can use the Session.createFilter() method. The only problem is, that it only supports HQL queries currently.
I recall this being an issue for something I did recently. Have you tried this:
if (picFilter != null && !picFilter.isEmpty()) {
Criteria subCriteria = crit.createCriteria("album.pictures"); // Or just 'pictures?'
Disjunction or = Restrictions.disjunction();
for (Integer id : picFilter)
or.add(Restrictions.idEq(id));
subCriteria.add(or);
crit.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
}
try this:
Criteria crit = getCurrentSession().createCriteria(Album.class, "album");
crit.add(Restrictions.idEq(albumId));
if (picFilter != null && !picFilter.isEmpty()) {
crit.createAlias("album.pictures", "picture");
crit.createAlias("picture.event", "event");
crit.add(Restrictions.in("event.id", picFilter));
crit.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
}
If you use alias with left_join, it will return just sub object which satisfy related condition. Otherwise it returns main object which satisfy conditions but with all of sub object set.
crit = crit.createAlias("album.pictures", "picture", CriteriaSpecification.LEFT_JOIN);
This method is deprrecated in some hibernate version, if so you can use below solution for it too:
criteria with filtered complex set
I'm using java persistence to save a list of entities that are associated to another entity. Here's a quick rundown of where I'm having some problems.
#Entity public class Offer implements Serializable {
#Id private Long offerId;
#OneToMany
#Column List<OfferCategory> offerCategories;
}
#Entity public class OfferCategory implements Serializable {
#Embeddable public static class Id implements Serializable
{
#Column(name="offer_id")
private Long offerId;
#Column(name="category_id")
private Long categoryId;
public Id() {}
public Id(Long offerId, Long categoryId) {
this.offerId = offerId;
this.categoryId = categoryId;
}
public boolean equals(Object o) {
if(o != null && o instanceof Id) {
Id other = (Id) o;
return this.offerId.equals(other.offerId) &&
this.categoryId.equals(other.categoryId);
}
else
return false;
}
public int hashCode() {
return offerId.hashCode() + categoryId.hashCode();
}
}
#EmbeddedId private Id id = new Id();
}
Essentially, due to an architecture I cannot change, I need to save a list of Categories as being assigned to an Offer.
Right now, I'm getting a list of Categories from the user and then putting them into the offerCategories field of Offer. However, this doesn't work for new Offers, because there's no way for me to set the ID of a new item.
I'm new to JPA and Seam, so if someone could give me a nudge in the right direction it would be greatly appreciated.
I have not tried using a composite ID before, but one thing to note is that #Column is only used to change the properties of the database column the field is using. It doesn't stipulate a relation, so you still need something like this:
#OneToMany
List<OfferCategory> offerCategories;
As I looked into tutorial I found this:
You cannot use an IdentifierGenerator to generate composite keys. Instead the application must assign its own identifiers.
So you have to assign the id by yourself. Maybe you can make a DB sequence and fetch its values with native query?
And one remark - if you want to use List mapping (order of Categories in Offer is defined by database), you need an index column to contain the index in list. If the order of categories is not important, Set would be more convenient.
Well, my solution for now is that I persist each one (creating keys for new entries), then stuff them into a list, then stuff them into their container object, then persist that object.