The Scenario
Class Employee, Class Office, Class OfficeEmployee.
Class office is a Spatial Entity, that can be searched and returns results as expected.
a manyToMany relationship between Office-Employee is mapped with the Class OfficeEmplyee.
Now I need to perform search based on certain people within some range. in other words I have to check for the offices in range and for emplyees who exist at those offices, i.e Searching the OfficeEmployee Entity.
All the three classes are indexed.
OfficeEmployee
// reference Spatial indexed entity Office
#IndexedEmbedded
#ManyToOne (cascade = CascadeType.MERGE)
#JoinColumn(name="office")
private Office office;
// reference to employee
#IndexedEmbedded
#JsonIgnore
#ManyToOne (cascade = CascadeType.MERGE)
#JoinColumn(name="employee")
private Employee employee;
Class Office
#JsonIgnoreProperties(ignoreUnknown=true)
#Spatial(name = "office_location_poi", spatialMode = SpatialMode.HASH )
#Indexed
#Entity
#Embeddable
public class Office implements Serializable,Coordinates {
// some attributes , getters , setters..
#ContainedIn
#OneToMany(mappedBy="office", cascade=CascadeType.ALL)
private List<OfficeEmployee > officeEmployees;
#Latitude
double latitude;
#Longitude
double longitude;
public Coordinates getLocation() {
return new Coordinates() {
#Override
public Double getLatitude() {
return latitude;
}
#Override
public Double getLongitude() {
return longitude;
}
};
}
#Override
public Double getLatitude() {
return latitude;
}
#Override
public Double getLongitude() {
return longitude;
}
}
The Query:
final QueryBuilder builder = fullTextEntityManager.getSearchFactory()
.buildQueryBuilder().forEntity( OfficeEmployee.class ).get();
double centerLatitude = searchTerm.lat;
double centerLongitude =searchTerm.lng;
org.apache.lucene.search.Query luceneQuery = builder.spatial().onField("office").within(searchTerm.distance, Unit.KM)
.ofLatitude(centerLatitude)
.andLongitude(centerLongitude)
.createQuery();
org.hibernate.search.jpa.FullTextQuery hibQuery = fullTextEntityManager.createFullTextQuery(luceneQuery, OfficeEmployee.class);
// sort
Sort distanceSort = new Sort(
new DistanceSortField(centerLatitude, centerLongitude, "office_location_poi"));
hibQuery.setSort(distanceSort);
hibQuery.setProjection(FullTextQuery.SPATIAL_DISTANCE, FullTextQuery.THIS);
hibQuery.setFirstResult(0);
hibQuery.setMaxResults(20);
// results
List<Office>results =hibQuery.getResultList();
The Problem
Now I want to perform my search on the relationship table (OfficeEmployee).
but sounds like I can't get it to work! I checked tutorials, and couldn't find such an example.
Is it possible to use a currently indexed Entity like I have explained?
Do I have to include a #Spatial there in the OfficeEmployee ? but that will require a new indexing separately, I want to use the currently indexed one.
When I run search it says that I need to check #Spatial and #SpatialFieldBridge, and even if I annotate so, the results are empty.
In case that my Spatial entity was implementing the coordinates and doesn't have a separate field for coordinates, where should the #ContainedIn be placed ?
Can anyone please point me in the right direction ?
Related
My native join query produces a new result that is a combination of database tables, so I created a dto for that resulting object (will be a list of records on a screen).
I believe I need to make it an entity, so JPA can recognize it, would that be the best way to do it?
Also, the entity needs an id, and I was hoping to let jpa generate it auto, but I'm getting "Invalid parameter: Unknown column name id. ERRORCODE=-4460, SQLSTATE=null"
My result set contains 4 of the same records instead of 4 different, and I think it has to do with my id field not set properly
Any help would be appreciated on the subject, thanks.
`public interface ErrorCodeRepo extends JpaRepository<Errors, ErrorsPK> {
#Query("SELECT e.transDate, e.category FROM Errors e")
List<QueuedErrors> findQueuedErrors();
}`
DTO class:
`
public class QueuedErrors {
private String transDate;
private String category;
public QueuedErrors(String transDate, String category) {
this.transDate = transDate;
this.category = category;
}
public String getTransDate() {
return transDate;
}
public void setTransDate(String transDate) {
this.transDate = transDate;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
}
`
When you create navite query which contains results from multiple tables (after joins) you don't have to create new entities.
Better way to solve this problem is to projection with interface or class DTO.
For example, if you want to combine results from Person and Address Entities, simply create interface:
public interface PersonView {
String getFirstName();
String getLastName();
String getStreet();
}
You can see combined fileds from Person (firstName, lastName) and Address (street).
You have to use it as query response, like this:
#Query(...)
List<PersonView> getPersonWithStreet(String state);
You can read more about it here:
https://www.baeldung.com/spring-data-jpa-projections
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections
I have reviewed multiple examples for how to construct a TreeTable from from a Container datasource and just adding items iterating over an Object[][]. Still I'm stuck for my use case.
I have a bean like so...
public class DSRUpdateHourlyDTO implements UniquelyKeyed<AssetOwnedHourlyLocatableId>, Serializable
{
private static final long serialVersionUID = 1L;
private final AssetOwnedHourlyLocatableId id = new AssetOwnedHourlyLocatableId();
private String commitStatus;
private BigDecimal economicMax;
private BigDecimal economicMin;
public void setCommitStatus(String commitStatus) { this.commitStatus = commitStatus; }
public void setEconomicMax(BigDecimal economicMax) { this.economicMax = economicMax; }
public void setEconomicMin(BigDecimal economicMin) { this.economicMin = economicMin; }
public String getCommitStatus() { return commitStatus; }
public BigDecimal getEconomicMax() { return economicMax; }
public BigDecimal getEconomicMin() { return economicMin; }
public AssetOwnedHourlyLocatableId getId() { return id; }
#Override
public AssetOwnedHourlyLocatableId getKey() {
return getId();
}
}
The AssetOwnedHourlyLocatableId is a compound id. It looks like...
public class AssetOwnedHourlyLocatableId implements Serializable, AssetOwned, HasHour, Locatable,
UniquelyKeyed<AssetOwnedHourlyLocatableId> {
private static final long serialVersionUID = 1L;
private String location;
private String hour;
private String assetOwner;
#Override
public String getLocation() {
return location;
}
#Override
public void setLocation(final String location) {
this.location = location;
}
#Override
public String getHour() {
return hour;
}
#Override
public void setHour(final String hour) {
this.hour = hour;
}
#Override
public String getAssetOwner() {
return assetOwner;
}
#Override
public void setAssetOwner(final String assetOwner) {
this.assetOwner = assetOwner;
}
}
I want to generate a grid where the hours are pivoted into column headers and the location is the only other additional column header.
E.g.,
Location 1 2 3 4 5 6 ... 24
would be the column headers.
Underneath each column you might see...
> L1
> Commit Status Status1 .... Status24
> Eco Min EcoMin1 .... EcoMin24
> Eco Max EcoMax1 .... EcoMax24
> L2
> Commit Status Status1 .... Status24
> Eco Min EcoMin1 .... EcoMin24
> Eco Max EcoMax1 .... EcoMax24
So, if I'm provided a List<DSRUpdateHourlyDTO> I want to convert it into the presentation format described above.
What would be the best way to do this?
I have a few additional functional requirements.
I want to be able to toggle between read-only and editable views of the same table.
I want to be able to complete a round-trip to a datasource (e.g., JPAContainerSource).
I (will eventually) want to filter items by any part of the compound id.
My challenge is in the adaptation. I well understand the simple use case where I could take the list and simply splat it into a BeanItemContainer and use addNestedContainerProperty and setVisibleColumns. Pivoting properties into columns seems to be what's stumping me.
As it turns out this was an ill-conceived question.
For data entry purposes, one could use a BeanItemContainer and have the columns include nested container property hour from the composite id and instead of a TreeTable, use a Table that has commitStatus, ecoMin and ecoMax as columns. Limitation: you'd only ever query for / submit one assetOwner and location's worth of data.
As for display, where you don't care to filter one assetOwner and location's worth of data, you could pivot the hour info as originally described. You could just convert the original bean into another bean suitable for display (where each hour is its own column).
Firstly, I have read Hibernate - One table with multiple entities?.
However, I would like to map two entities to the same table, but I would like both of them to be entities, which I can select from. What I mean:
One table: Person (id, name, dateOfBirth, city, street, zipcode).
Two Entities: Person (id, name, dateOfBirth), Address (id, city,
street, zipcode).
So it's a 1:1 relationship between Entities, but still 1 table in DB.
If I do it using the proposed solution (component keyword) in the above link, I can't query Address directly (I can access it via Person entity). And I want to be able to do
session.createCriteria(Adres.class)
How do I do that?
UPDATE:
I tried the one-to-one association between entities, in Address mapping:
<one-to-one name="Person " class="model_mapowanie_xml.Person "/>
and in Person mapping:
<one-to-one name="Address" class="model_mapowanie_xml.Address "/>
Both classes have fields referring to the other one. Selecting records works fine for that. However, how can I add in one transaction a record using both entities? (Id is db-generated)
Address ad = new Address();
ad.setProperty("Sydney");
Person p = new Person();
p.setProperty("John");
p.setAddress(ad);
session.save(p);
and only Person part is saved, the address property remains empty.
This is very simple to achieve with JPA and Hibernate.
Let's assume you are using the following book database table:
Mapping entities
Now, you can map two entities: Book and BookSummary to this table.
First, we will create a BaseBook abstract class which will be extended by all entities:
#MappedSuperclass
public abstract class BaseBook<T extends BaseBook> {
#Id
#GeneratedValue
private Long id;
#NaturalId
#Column(length = 15)
private String isbn;
#Column(length = 50)
private String title;
#Column(length = 50)
private String author;
public Long getId() {
return id;
}
public T setId(Long id) {
this.id = id;
return (T) this;
}
public String getIsbn() {
return isbn;
}
public T setIsbn(String isbn) {
this.isbn = isbn;
return (T) this;
}
public String getTitle() {
return title;
}
public T setTitle(String title) {
this.title = title;
return (T) this;
}
public String getAuthor() {
return author;
}
public T setAuthor(String author) {
this.author = author;
return (T) this;
}
}
Now, the BookSummary entity simply extends the BaseBook superclass and adds no additional entity attribute.
#Entity(name = "BookSummary")
#Table(name = "book")
public class BookSummary extends BaseBook<BookSummary> {
}
On the other hand, the Book entity extends the BaseBook superclass and maps the properties attribute.
#Entity(name = "Book")
#Table(name = "book")
#TypeDef(
name = "jsonb",
typeClass = JsonBinaryType.class
)
#DynamicUpdate
public class Book extends BaseBook<Book> {
#Type(type = "jsonb")
#Column(columnDefinition = "jsonb")
private String properties;
public String getProperties() {
return properties;
}
public Book setProperties(String properties) {
this.properties = properties;
return this;
}
public ObjectNode getJsonProperties() {
return (ObjectNode) JacksonUtil
.toJsonNode(properties);
}
}
Persisting entities
This way, you can persist either a Book entity:
entityManager.persist(
new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea")
.setProperties(
"{" +
" \"publisher\": \"Amazon\"," +
" \"price\": 44.99," +
" \"publication_date\": \"2016-20-12\"," +
" \"dimensions\": \"8.5 x 1.1 x 11 inches\"," +
" \"weight\": \"2.5 pounds\"," +
" \"average_review\": \"4.7 out of 5 stars\"," +
" \"url\": \"https://amzn.com/973022823X\"" +
"}"
)
);
or a BookSummary:
entityManager.persist(
new BookSummary()
.setIsbn("978-1934356555")
.setTitle("SQL Antipatterns")
.setAuthor("Bill Karwin")
);
Fetching entities
You can fetch the BookSummary entity:
BookSummary bookSummary = entityManager
.unwrap(Session.class)
.bySimpleNaturalId(BookSummary.class)
.load("978-9730228236");
assertEquals(
"High-Performance Java Persistence",
bookSummary.getTitle()
);
or the Book entity if you want:
Book book = entityManager
.unwrap(Session.class)
.bySimpleNaturalId(Book.class)
.load("978-9730228236");
assertEquals(
"High-Performance Java Persistence, 2nd edition",
book.getTitle()
);
Conclusion
So mapping multiple entities to the same database table, not only that it allows us to fetch data more efficiently, but it also speeds up the dirty checking process as Hibernate has to inspect fewer entity properties.
The only drawback of using this approach is that you have to make sure you don’t fetch more than one entity type for the same database table record, as otherwise, this can cause inconsistencies when flushing the Persistence Context.
You should be able to do it using #Table annotation. These entites will be treated as different entites but will be mapped onto same table.
#Entity
#Table(name="PERSON_TABLE")
class Person {}
#Entity
#Table(name"PERSON_TABLE")
class Address {}
Edit:
If you want to save both entities in one transaction you either have to explicitly save them using Session or set cascade property to cascade operations on relationship. I guess you want to cascade operations on Address when you do something on Person. See CascadeType if you use annotations.
In your hbm it would look like
<one-to-one name="Person" class="model_mapowanie_xml.Person" cascade="all"/>
I am a bit lot about what to do in an OO/DB relation...
Here is the DB model :
CREATE TABLE User
Id
CREATE TABLE Location
userId
// EDIT oups, wrong !
// placeId
// Should be :
seatId
CREATE TABLE Game
locationId
Now some code :
class User
{
private Location locations[]; // need this for several reasons...
public function loadFromDatabase()
{
// Load data from DB
// ...
result = DB::query("SELECT Id FROM Locations WHERE userId="+this->Id);
foreach(result)
{
l = new Location();
l->loadFromDatabase(result);
locations[] = l;
}
}
}
class Location
{
private User user;
public function loadFromDatabase()
{
...
}
}
class Game
{
private Location location;
public loadFromDatabase()
{
/*
Here comes the problem :
how to have a reference to a location
created by the User class ?
*/
}
}
A User play Games in several Locations.
EDIT : And for each location the user plays on seat. Or on another seat...
When I want to know where a game has been played I access Game.location. And when I want to know who played it, I access Game.location.user
Here is my problem : I want the Game.location to be the same reference to one of the User.locations and I do not know how to do this...
And, globally, I feel something wrong about my code...
Any help ?
Thanks
Since you have a placeId in your Location table, I assume there is a Place table which describes what the places actually are, while the Location table simply represents the many-to-many mapping between users and places.
In that case, Location doesn't need to have an Id of its own and doesn't need to be a class, but Place does.
To load just one instance of each object from the database, cache the instances in a static map inside each class.
class Place
{
// Static
private static Place loadedPlaces[];
public static function get(id)
{
if (!loadedPlaces[id])
{
loadedPlaces[id] = new Place(id);
loadedPlaces[id]->loadFromDatabase();
}
return loadedPlaces[id];
}
// Non-static
private id;
public function loadFromDatabase()
{
// ...
}
}
Then to get references to places for the properties of a user or a game, you just access them via the static method.
class User
{
public function loadFromDatabase()
{
result = DB::query("SELECT placeId FROM Locations WHERE userId="+this->Id);
foreach(result)
{
places[] = Place::get(result);
}
}
}
class Game
{
public function loadFromDatabase()
{
place = Place::get(place);
}
}
This uses:
Lazy initialization, because places are only loaded when they are needed.
Multiton pattern, because there is only one instance of each place by id.
Not quite a factory method, because there's no object hierarchy involved.
I'm trying to get a specific set of data while joining 4 different entities together to do so. What I've done is setup a DTO to try to get this working:
public class LatestThread
{
private readonly string comment;
private readonly DateTime posted;
private readonly string userName;
private readonly int reputation;
private readonly int threadId;
private readonly string topic;
private readonly int userId;
private readonly string avatar;
public LatestThread(string comment, DateTime posted, string userName, int reputation, int threadId, string topic, int userId, string avatar)
{
this.comment = comment;
this.avatar = avatar;
this.userId = userId;
this.topic = topic;
this.threadId = threadId;
this.reputation = reputation;
this.userName = userName;
this.posted = posted;
}
public string Comment
{
get { return comment; }
}
public DateTime Posted
{
get { return posted; }
}
public string UserName
{
get { return userName; }
}
public int Reputation
{
get { return reputation; }
}
public int ThreadId
{
get { return threadId; }
}
public string Topic
{
get { return topic; }
}
public int UserId
{
get { return userId; }
}
public string Avatar
{
get { return avatar; }
}
}
Now I thought I could use SimpleQuery like so:
string hql = string.Format("select new LatestThread(m.Comment, m.Posted, u.UserName, u.Reputation, t.Id, t.Topic, u.Id, u.Avatar) from Thread as t inner join Message as m on t.Id = m.ThreadId inner join User as u on u.Id = m.PostedById inner join Activity as a on a.Id = t.ActivityId where a.Lineage like '{0}%' order by t.LastPosted desc", activityLineage);
return repository.SimpleQuery(0, 10, hql);
My repository method looks like:
public virtual IList<T> SimpleQuery<T>(int firstResult, int maxResults, string hql, params object[] parameters)
{
var query = new SimpleQuery<T>(hql, parameters);
query.SetQueryRange(firstResult, maxResults);
return query.Execute();
}
Now it's asking for me to put [ActiveRecord] at the top of my LatestThread class. When I do that it wants a primary key, and that just seems to be the wrong route.
I've also read bits that refer to the Import attribute given to classes that aren't the DTO. In all the examples though it's just two entities being joined, not the 4 I have. Do I need to add Import to all 4? Or is there something to tell AR that it's a readonly DTO class? OR am I doing this all wrong and there's a really easy way to do what I'm trying to do.
TIA!
Add the Import attribute to your new Thread class
[Import(typeof(LatestThread), "LatestThread")]
[ActiveRecord("Thread")]
public class Thread : ActiveRecordBase<Thread> { /* blah blah */ }
And then, query magic happens :)
string hql = string.Format("select new LatestThread(m.Comment, m.Posted, u.UserName, u.Reputation, t.Id, t.Topic, u.Id, u.Avatar) from Thread as t inner join Message as m on t.Id = m.ThreadId inner join User as u on u.Id = m.PostedById inner join Activity as a on a.Id = t.ActivityId where a.Lineage like '{0}%' order by t.LastPosted desc", activityLineage);
SimpleQuery<LatestThread> query = new SimpleQuery<LatestThread>(typeof(Thread), hql );
LatestThread[] results = query.Execute()
Source : http://www.kenegozi.com/Blog/2006/10/08/projection-using-activerecords-importattribute-and-hqls-select-new-clause.aspx
You can't query a type that isn't mapped (which is what the [ActiveRecord] attribute does). AFAIK you can't get NHibernate to create a new arbitrary object instance like that via HQL (I stand to be corrected if someone knows otherwise).
Your best bet is to do a projection query and then have a method to map the tuples returned into instances of your type.
My answer here shows how to do a projection query and map it to an anonymous type; what you want to do is not much different. You could then put a method to do this in a type-specific repository or a strongly-typed extension method to the generic repository.