Having the weirdest issue here, all is working fine, except that my 1-to-M query is duplicating the data.
Customer table
#Entity(tableName = "customer_table")
public class Customer {
#ColumnInfo(name = "Customer_Serial", index = true)
#PrimaryKey
private int customerSerial;
#ColumnInfo(name = "Customer_Name")
private String customerName;
public Customer(int customerSerial, String customerName) {
this.customerSerial = customerSerial;
this.customerName = customerName;
}
}
Invoice table
#Entity(tableName = "invoice_table")
public class Invoice {
#ColumnInfo(name = "Invoice_Number", index = true)
#PrimaryKey
private int invoiceNumber;
#ColumnInfo(name = "Customer_Serial")
private int customerSerial;
public Invoice(int invoiceNumber, int customerSerial) {
this.invoiceNumber = invoiceNumber;
this.customerSerial = customerSerial;
}
}
CustomerInvoice relation
public class CustomerInvoice {
#Embedded public Customer customer;
#Relation(
parentColumn = "Customer_Serial",
entityColumn = "Customer_Serial"
)
public List<Invoice> invoices;
}
DAO
#Transaction
#Query("SELECT * FROM customer_table INNER JOIN invoice_table ON invoice_table.Customer_Serial = customer_table.Customer_Serial")
List<CustomerInvoice> getAllCustInvoices();
#Insert
void insertInvoice(Invoice... invoice);
#Insert
void insertCustomer(Customer... customer);
If I debug my application, set a breakpoint to test the Room stuff, then use the 'Evaluate' feature in Android Studio, I do the following
Invoice invoice1 = new Invoice(1234, 1);
Invoice invoice2 = new Invoice(2468, 1);
Customer customer = new Customer(1, "Test Customer");
dao.insertCustomer(customer);
dao.insertInvoice(invoice1);
dao.insertInvoice(invoice2);
If I then retrieve the information using getAllCustInvoices()
The list returned has 2 in it.
It has the customer duplicated for each invoice assigned to them, and then both invoices listed in each 1.
I'm not entirely sure where I am going wrong here, this is a simplified example of what the app itself is actually doing, simplified enough to see if something else in my code was causing the problem or not.
Turns out, even with the simplified example, it has the issue.
The issue
When #Relation is used Room extracts the children (ALL children) per parent (effectively running a second query to extract the children, hence the recommendation for using #Transaction ). By specifying the JOIN you are extracting the same parent for each child (i.e. the cartesian product) and hence the duplication.
i.e. Room does the equivalent of the JOIN internally
The Fix
#Transaction
#Query("SELECT * FROM customer_table")
List<CustomerInvoice> getAllCustInvoices();
Related
I've recently started using LiveData in my project and was facing difficulties JOINing data from related tables. Not being able to find anything related to this on the web - links welcome, please, I started this morning to try and find a solution.
So, I put together a Product Entity class…
#Entity(tableName = "products")
public class Product {
#PrimaryKey(autoGenerate = true)
#NonNull
int productID;
#NonNull
private String productName;
#NonNull
private String supplierName;
private int supplierID;
private int stocklevel;
public Product(#NonNull String productName, int supplierID) {
this.productName = productName;
this.supplierID = supplierID;
this.supplierName = "Dummy Supplier";
stocklevel = 0;
}
and a DAO with the query…
#Query("SELECT products.*, suppliers.supplierName FROM products JOIN suppliers ON products.supplierID = suppliers.supplierID ORDER BY stocklevel DESC")
LiveData<List<Product>> getProductDetails();
so that, when the query is run, the placeholder text is overwritten by the supplierName from the related table. And it works!
So, my question is, 'Is this an acceptable way of proceeding? And, if not - what is?'
Any feedback? Or links?
I am showing a grid table whit the service names with the last price register. The problem is happen when I use the #Transient in my Service class.
In this case:
I do this:
public List<Service> findAllWithPrice() {
NativeQuery<Service> query =
this.getCurrentSession()
.createSQLQuery(
"select s.*, FORMAT((select ps.price from priceServices ps where ps.idService = s.id order by ps.dateRegister DESC limit 1),2) as currentPrice from service s");
query.addEntity( Service.class );
return query.getResultList();
}
/*********************/
#Entity
#Table(name = "service")
public class Service {
/****/
#Transient
private String currentPrice;
public String getCurrentPrice() {
if ( currentPrice == null ) {
return "$ 0.0";
}
return currentPrice;
}
}
If I leave #Transient both save and select work but ALL prices come as zero. The currentPrice is coming null.
If I remove #Transient the select comes right. It loads the services with the last registered price for each one.
But when I save to the bank, it returns an error saying that it did not find the currentPrice column (Because it really doesn't exist).
I searched here in the forum and on the internet but I didn't find a solution.
How can I solve this problem?
Thanks to the #M.Prokhorov Tip, I was able to solve my problem as follows:
In my ServiceDaoImpl class, I stopped using the findAllWithPrice method to use findAll only:
public List<Service> findAll() {
return this.getCurrentSession().createQuery("from Service", Service.class).getResultList();
}
In my Service class I created a formula to fetch the last recorded price
#Entity
#Table(name = "service")
public class Service {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
/****/
#Formula("(FORMAT((select ps.price from priceServices ps where ps.idService = id order by ps.dataRegister DESC limit 1),2))")
private String currentPrice;
public String getCurrentPrice() {
if ( currentPrice == null ) {
return "$ 0.0";
}
return currentPrice;
}
}
My initial requirement was to fetch Car details as:
List<String> carMake = getUserCarMake();
Page<Car> carDetails = carRepository.findAllCarsInfo(carMake, pageRequest);
CarRepository.java
#Query(value="SELECT A FROM Cars A WHERE A.model = ?1
public Page<Cars> findAllCarsInfo(List<String> carMake, Pageable pageRequest);
Now my requirement has changed to fetch car details based car models for each car make. So I have changed the code as shown
for (Cars car : userCars) {
String carMake = car.getCarMake();
List<String> carModelForMake = new ArrayList<>();
List <CarModels> carModelList = car.getCarModels();
for (CarModels carModel : carModelList) {
carModelForMake.add(carModel.getModelName());
Page<Car> carDetails = carRepository.findAllCarsInfo(carModelForMake, carMake, pageRequest)
}
}
CarRepository.java
#Query(value="SELECT A FROM Cars A WHERE A.model IN ?1 AND A.make = ?2”
public Page<Car> findAllCarsInfo(List<String> carModel, String carMake,Pageable pageRequest);
So for each car i have a carMake and corresponding carModels for that make which i then pass to the query to fetch carDetails which is a Page Object. As a result same query is called multiple times for different carMake.
The problem is how do I manage the Page object here. In the above scenario the Page object will contain only the details of last car from the carModelList, rest will be overwritten as I do not
have an option of carDetails.addAll() as in case of List.
Note: I cannot use the below query as the model can overlap across different makes.
SELECT A FROM Cars A WHERE A.model IN ?1 AND A.make IN ?2
Also my pageRequest has size as (0, 20 )
I have tried to modify the query to remove pageRequest and use findAll to fetch the results in List and then convert them to PageObject but that breaks the pagination because the page.getContent() has the entire result set and not just 20 records.
Page<Car> carDetails = new PageImpl <>(carDetailsList, pageRequest, carDetailsList.size());
How can I effectively get Page object or merge different page objects here so that my pagination works as it did in my previous requirement.
Sometimes it is a good idea to create a special "query entity" class that includes everything that is needed to respond to a certain kind of client request.
The general idea is like this:
Let's say you'd have two classes in you domain:
#Entity
#Table(name = "table_a")
public class A {
#Id int id;
String propertyA;
int bId;
}
#Entity
public class B {
#Id int id;
String propertyB;
}
And then you'd combine the two two the mentioned "query entity" (outside of the domain).
#Entity
#Table(name = "table_a")
public class QueryEntity {
private #Id private int aId;
private String propertyA;
private B b;
public String propertyA() {
return propertyA;
}
public String propertyB() {
return b.propertyB;
}
}
I'm not quite sure whether this approach is applicable in your case, but hopefully it makes the idea clear.
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 an EJB many-to-many (bi-directional) relation between classes (entity-classes) Person and Hobby. There are corresponding tables in the database, called PERSON and HOBBY, as well as a table PERSON_HOBBY for the many-to-many relationship.
As I will detail below, the problem is that whenever I try to persist a person with hobbies, I run into a Foreign Key constraint violation. This is because the entityManager tries to save new rows into PERSON_HOBBY that contain references to a person-entity with ID=0, which doesn’t exist in the PERSON table. I’ll come back to that later, but first I’ll show the relevant parts of the entity classes.
First, here is entity class Person:
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(nullable = false)
private String name;
#Column(nullable = false)
private String email;
#ManyToMany(cascade = {CascadeType.MERGE}, fetch = FetchType.EAGER)
/* Note: I used to have CascadeType.PERSIST in the above line as well, but
it caused "Detached object passed to persist" exceptions whenever I tried to
persist a person with hobbies. So I suppose I was right in deleting
CascadeType.PERSIST...? */
#JoinTable(name = "PERSON_HOBBY",
joinColumns = #JoinColumn(name="personId", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="hobbyId", referencedColumnName="id"))
private List<Hobby> hobbies = new ArrayList<Hobby>();
public List<Hobby> getHobbies () {
return hobbies;
}
public void setHobbies (List<Hobby> hobbies) {
this.hobbies = hobbies;
for(Hobby h:hobbies) { // this is to maintain bi-directionality
h.addPerson(this);
}
}
// other getters and setters omitted here.
Then entity class Hobby:
#Entity
public class Hobby {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(nullable = false)
private String description;
#ManyToMany(mappedBy = "hobbies", fetch = FetchType.EAGER)
private List<Person> persons;
public Hobby() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
// getter and setter for Description omitted here.
public List<Person> getPersons () {
return persons;
}
public void setPersons (List<Person> persons) {
this.persons = persons;
}
public void addPerson (Person p) {
this.persons.add(p);
}
}
I also have a stateless session bean, that’s shown here as far as relevant to the issue:
#Stateless
#Default
public class PersonRepositoryImpl implements PersonRepository {
#PersistenceContext
private EntityManager entityManager;
#Override
public Person create(Person p) {
entityManager.persist(p);
entityManager.flush();
return p;
}
#Override
public Person createPersonWithHobbies(Person p, List<Hobby>hobbyLijst) {
p = create(p); // I've also tried simply: create(p);
System.out.println("The newly assigned ID for the persisted
person is: " + p.getId());
// That last line **always** prints the person-ID as being 0 !!!!
p.setHobbies(hobbyLijst);
entityManager.merge(p); // This should save/persist the person's hobby's!
entityManager.flush();
return p;
}
}
Now from my servlet, I've been trying in two different ways. First, I tried calling method create(p) on the above session bean. That is, after creating a new Person instance p, setting all its non-relational fields, AND calling setHobbies on it (with a non-zero list of Hobby objects taken from the database), I called:
personRepo.create(p);
But this resulted in the Foreign Key (FK) exception:
INSERT on table 'PERSON_HOBBY' caused a violation of foreign key
constraint 'FK_EQAEPVYK583YDWLXC63YB3CXK' for key (0). The statement
has been rolled back.”
The FK-constraint mentioned here is the one in PERSON_HOBBY referring to PERSON.
The second way I tried was to make the following call from the servlet:
personRepo.createPersonWithHobbies(p, hobbyLijst);
where, just like before, p is the new person object; and hobbyLijst is that person's list of hobbies. And this resulted in the exact same FK-exception as the earlier call to personRepo.create(p).
Importantly, the println statement within method createPersonWithHobbies, calling getId() on the newly persisted person-object, ALWAYS gives that object's ID as being 0. Which I suppose does explain the FK-exception, since there's no person entity/row in the PERSON table with an ID of 0, nor is there supposed to be one. But of course the getId() call should not output 0. Instead, it should output the newly generated ID of the newly persisted person entity. (And yes, it IS persisted correctly in the PERSON tabel, with a correctly generated ID>0. So the correct ID is there in the PERSON-table - it just seems to be invisible to the entityManager and/or the container.)
Thanks.