So I am trying to implement a single INSERT(and other CRUD operations) method for different entities:
#Table(name = "CLIENT")
public class Client {
#Column(name = "ID", isPrimaryKey = true)
private long id;
#Column(name = "FULL_NAME")
private String name;
#Column(name = "MONEY")
private double money;
//setters and getter
}
In my Service I have:
public void add(Client c){
clients.add(c);
persistent.add(c.getClass());
}
This method add(Client c) will call persistent.add(c.getClass()). This method looks like:
public void add(Class<?> classOfObjectToBeAdded){
PreparedStatement pstmt = null;
queryCreator = new AddQueryCreator(classOfObjectToBeAdded);
String finalQuery = queryCreator.getQuery();
System.out.println(finalQuery);
//add to database with prepared statement
}
Now, because this add method is an INSERT query, I need to dynamically create the INSERT based on the object I give. I have that AddQueryCreator that will create my INSERT. And this is my problem, how do I do this? An example would be: INSERT INTO mydb.CLIENT VALUES(NULL,"John",5000).
I managed to scan the class for the class annotations to get the table's name and I managed to scan the fields and take the annotations + get those values from the annotation:
(...)
public #interface Column {
String name();
boolean isPrimaryKey() default false;
}
(...)
public #interface Table {
String name();
}
For having another entity, ex: Fireman, my INSERT would look like INSERT INTO mydb.FIREMAN VALUES(NULL,"Nick",25,5,...) where 25 is the age and 5 is the department. I put NULL, because that field is the PK and has AUTO INCREMENT.
How do I create that INSERT (and the other CRUD operations) dynamically?
I do not want to use existing ORMs because I am trying to learn stuff.
Related
I am trying to extract data using composite key of other table, but here problem is, I have list of composite key. Below are the table.
#Embeddable
public class IdRangePk implements Serializable {
#Column("START_RANGE")
private String startRange;
#Column("END_RANGE")
private String endRange;
// default constructor
}
#Entity
#Table(name = "ID_RANGE")
public class IdRange {
#EmbeddedId
private IdRangePk idRangePk;
#Column(name = "PARTY_ID")
private String partyId;
#Column("STATUS")
private String status; // expired or active
// constructors, other fields, getters and setters
}
Here, ID_RANGE have composite primary key (START_RANGE, END_RANGE). So same PARTY_ID can have multiple combination of start & end range.
STATUS can be either "EXPIRED" or "ACTIVE".
#Entity
#Table(name = "MESSAGE")
public class Message {
#Id
#Column("MESSAGE_Id")
private String id;
#Column(name = "PARTY_ID")
private String partyId;
#Column("START_RANGE")
private String startRange;
#Column("END_RANGE")
private String endRange;
// default constructor
// constructors, other fields, getters and setters
}
Here, I need to extract the messages having active ranges for a given PARTY_ID. Also MESSAGE_ID is the primary key.
So I divided it into two steps
Extracting active ranges for given party id
#Repository
public interface IdRangeRepo extends JpaRepository<IdRange, IdRangePk> {
List<IdRange> findByPartyIdAndStatus(String partyId, String status);
}
List<IdRange> idRanges = findByPartyIdAndStatus("123", "ACTIVE");
Extracting message from list of active IdRange
List<String> startRange = new ArrayList<>();
List<String> endRange = new ArrayList<>();
idRanges.stream().forEach(range -> {
startRange.add(range.getStartRange());
endRange.add(range.getEndRange())
});
List<Message> activeRangeMessage = findByPartyIdAndStartRangeInAndEndRangeIn("123", startRange, endRange);
#Repository
public interface MessageRepo extends JpaRepository<Message, String> {
List<IdRange> findByPartyIdAndStartRangeInAndEndRangeIn(String partyId, List<String> startRange, List<String> endRange);
}
My second step query is not right, it is extracting more rows than the expected as query is counter by individually field instead of whole (for startRange & endRange which is a composite key). Can someone please help me correct my query or provide an easiest way to extract rows. I have used derived method but #Query will also do.
I've spent the last couple of hours trying to figure this out, mostly by searching since I figure someone has already done it, but I'm not finding an answer that works for me.
Here's an on-the-fly translation of my code to something I can put in public.
#Entity #Table(name="result")
public class Result implements Serializable {
#Embeddable
public static class ResultPK implements Serializable {
#Column(name="result_date", nullable=false)
#Type(type="com.example.HibernateUTC$LocalDateType") // <- UserType
public LocalDate resultDate;
#Column(name="name", nullable=false)
public String name;
#Column(name="category", nullable=false)
public String category;
public ResultPK() {}
public ResultPK(Date resultDate, String name, String category) {
this.resultDate = resultDate;
this.name = name;
this.category = category;
}
// ...more code for hashCode/equals, no setters or getters...
}
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name="resultDate", column=#Column(name="result_date", nullable = false)),
#AttributeOverride(name="name", column=#Column(name="name", nullable=false)),
#AttributeOverride(name="category", column=#Column(name="category", nullable = false)),
})
private ResultPK resultId;
#Column(name="r_square")
private Double rSq;
#Column(name="p_value")
private pValue;
// ... more code for other fields, setters, getters, but nothing else; vanilla pojo...
}
I have a DAO where queries are hiding; the method that I'm calling is this
#Repository("resultDAO")
public class ResultDAOImpl extends AbstractBaseDAO<Result> implements ResultDAO {
// boilerplate for intializing base class and other queries
#Override
public List<Result> findDateRange(String category, String name, LocalDate begDate, LocalDate endDate) {
EntityManager em = entityManagerFactory.createEntityManager();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Result> q = cb.createQuery(Result.class);
Root<Result> root = q.from(Result.class);
Predicate catMatch = cb.equal(root.get("resultId.category"), category);
Predicate nameMatch = cb.equal(root.get("resultId.name"), name);
Predicate dateRange = cb.between(root.get("resultId.resultDate"), begDate, endDate);
q.select(root).where(cb.and(catMatch, nameMatch, dateRange));
return em.createQuery(q).getResultList();
}
}
When I attempt to run the code that executes that query, I end up with an error
Exception in thread "main" java.lang.IllegalArgumentException: Unable to locate Attribute with the the given name [resultId.category] on this ManagedType [com.example.Result]
Some of the similar questions I've found made it look like I needed to use the resultPK or ResultPK in the query. I've tried those, no joy. I have no idea how to specify the fields in the key for the query, or if I need something totaly different to this. I really need a clue...
I'm using Spring 4.3.8.RELEASE and Hibernate 4.3.11.Final, Java 8 (hence the UserType to handle LocalDate).
Edited to correct some inconsistencies in my transcription of the actual code.
You should modify your predicates this way:
Predicate catMatch = cb.equal(root.get("resultId").get("category"), category);
Predicate nameMatch = cb.equal(root.get("resultId").get("name"), name);
Predicate dateRange = cb.between(root.get("resultId").get("resultDate"), begDate, endDate);
By the way no need to use cb.and inside of where statement. You can make the code a bit shorter using
q.select(root).where(catMatch, nameMatch, dateRange);
I have this rest point which is used to return IDs:
#GetMapping("{id}")
public ResponseEntity<?> get(#PathVariable String id) {
return contractService
.findById(Integer.parseInt(id))
.map(mapper::toNewDTO)
.map(ResponseEntity::ok)
.orElseGet(() -> notFound().build());
}
DTO:
public class ContractNewDTO {
.....
private Integer terminal_id;
....
}
How I can translate terminal_id into terminal name using second SQL query?
I need something like this:
......map(mapper::toNewDTO) -> get here terminal_id and make another SQL query to find which name relates to this terminal_id and return the terminal name NOT terminal_id.
Can you give me some advice how to do this?
So you want to retrieve terminal_name based on terminal_id.
You have a couple of options:
1) If terminal_name is in the same database table as temrminal_id, then it should be loaded inside ContractNewDTO in your mapper::toNewDTO you can implement the conversion logic which uses terminal_name instead of temrminal_id.
2) If termminal_name is in another table (e.g. terminaldetails table) and you will need all the data from that table, then you can create a mapping (e.g. OneToOne joined on the terminal_id). Here is an excellent writing on how to do OneToOne mapping, from the master himself.
3) Another option is to use #SecondaryTable. Let's say your current entity is TerminalEntity and it has column terminal_id and is mapped to table "terminal". The terminal_name is in another table "terminaldetails".
What you can do is:
#Entity
#Table(name = "terminal")
#SecondaryTable(name = "terminaldetails")
public class TerminalEntity {
#Id
#GeneratedValue
private Long id;
#Column(name = "terminal_id")
private String terminalId;
#Column(name = "terminal_name", table = "terminaldetails")
private String terminalName;
}
I am trying to save data in table using hibernate. My entity looks like
#Entity
public class Nemocnica implements java.io.Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
//#Column(name="N_ID")
private BigDecimal NId;
private String adresa;
private Set sanitkas = new HashSet(0);
public Nemocnica() {
}
public Nemocnica( String adresa) {
this.NId = NId;
}
public Nemocnica(BigDecimal NId, String adresa) {
this.NId = NId;
this.adresa = adresa;
}
public Nemocnica(BigDecimal NId, String adresa, Set sanitkas) {
this.NId = NId;
this.adresa = adresa;
this.sanitkas = sanitkas;
}
public BigDecimal getNId() {
return this.NId;
}
public void setNId(BigDecimal NId) {
this.NId = NId;
}
public String getAdresa() {
return this.adresa;
}
public void setAdresa(String adresa) {
this.adresa = adresa;
}
public Set getSanitkas() {
return this.sanitkas;
}
public void setSanitkas(Set sanitkas) {
this.sanitkas = sanitkas;
}
}
And the way i want to insert data into it
public static Integer addNemocnica( String adresa ){
Integer ID = null;
try{
Nemocnica n = new Nemocnica( adresa);
ID = (Integer) session.save(n);
tx.commit();
}catch (HibernateException e) {
if (tx!=null) tx.rollback();
e.printStackTrace();
}
return ID;
}
Yet it still throws error
ids for this class must be manually assigned before calling save()
I tried everything i found including
#GeneratedValue(strategy=GenerationType.IDENTITY)
I am using oracle database and the table i am trying to insert data into has set autoincrement to true. How can fix this?
Thanks for help
Edit 4:
It's not clear how is your tables setup in DB. I am assuming tables are setup like this:
CREATE TABLE `Nemocnica` (
`NId` DECIMAL(4,2) NOT NULL AUTO_INCREMENT,
`adresa` VARCHAR(255) NULL DEFAULT NULL,
....
PRIMARY KEY (`NId`)
)
If that's how your table is created, then
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="N_ID")
private BigDecimal NId;
should work. Try matching the names of the columns in the table to the fields in the Java class. If not, then show us your sql table how it was created. If you don't know how to get the table creation schema, you will need to look it up online for your specific Database. You may also want to lookup how to get the sequence schema out of the Database (if schema exists, you will find it).
End of Edit 4.
If you are using a sequence in Oracle database, then GenerationType.IDENTITY is not correct. you need to provide GenerationType.SEQUENCE in strategy attribute. Code would will look like this:
#GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "SomeEntityGenerator")
#SequenceGenerator(name = "SomeEntityGenerator",
sequenceName = "SomeEntitySequence")
private BigDecimal NId;
But the above code will create a new sequence. If you already have a sequence ready and setup in Oracle DB then you would need to provide:
#GeneratedValue(generator="my_seq")
#SequenceGenerator(name="my_seq",sequenceName="MY_SEQ", allocationSize=1)
If you go this route, you have to specify the allocationSize which needs to be the same value that the DB sequence uses as its "auto increment".
Try this:
#GeneratedValue(strategy=GenerationType.AUTO)
#SequenceGenerator(name="my_entity_seq_gen", sequenceName="MY_ENTITY_SEQ")
sequenceName attribute is the name of the sequence in DB. So you will put that name in that attribute.
The last resort is to manually retrieve the value of the sequence from DB. Then set it yourself. Retrieving part would look like this
Long getNext() {
Query query =
session.createSQLQuery("select MYSEQ.nextval as num from dual")
.addScalar("num", StandardBasicTypes.BIG_INTEGER);
return ((BigInteger) query.uniqueResult()).longValue();
}
But for this to work you would have to modify your code, which I suggest you don't.
Does you set the autoincrement flag in your database for the primary key n_id?
If you use tx.commit() you need to start the transaction with tx.begin()
Does the entity properties correctly mapped with the db columns. Use the #Column Annotations. In your example it's commented out. Do you have active the auto validation at startup you can set it in the persistence.xml?
I am using a JPA model with two classes. The first one is mapping a table with "dynamic" data, the second one is mapping a table with read-only, reference data.
As an example, I have a Person entity mapping a Person Table, that contains a #OneToOne reference to the Civility entity, which itself maps to the Civility table (2 columns) that only has 3 records in it (Miss, Mrs and Mr).
I wanted to know the best way to write a query on the person entity based on Civility value. For example, what query would I use to get all Person's with civility = Mr?
Thanks.
one way to map reference lookup data is to use the #Enumerated annotation in jpa. You still have to create enumeration with the lookup values, but that's why it's reference data anyway.
For example, I have a rating code, and its a string/varchar value on table.
But can use a enumeration to use it:
#Enumerated(EnumType.STRING)
#Column
public RatingCode getRating() {
return rating;
}
public void setRating(RatingCode rating) {
this.rating = rating;
}
and the enumeration is:
public enum RatingCode {
Core, Star
}
Use a unit test to try all values, and you know it's a safe way to get reference data.
You can still use HQL to pull out the values, and pass the enumeration as the value:
hql = "select r from Rating as r where r.rating = :aEnum"
// and in the call to pass the parameter
qry.setParameter("aEnum", aRatingCode)
The enumeration is a field within the Rating entity:
#Entity
#Table
public class Rating {
private Integer rating_Id;
private RatingCode rating;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column
public Integer getRating_Id() {
return rating_Id;
}
public void setRating_Id(Integer rating_Id) {
this.rating_Id = rating_Id;
}
#Enumerated(EnumType.STRING)
#Column
public RatingCode getRating() {
return rating;
}
public void setRating(RatingCode rating) {
this.rating = rating;
}
}
So I have a profile, that requires a Rating, so I lookup a rating via the enumeration and add it to the profile.
Profile p = new Profile();
RatingServiceI rs = new RatingService()
Rating r = rs.getRating(RatingCode.Core);
p.setRating(r);
You didn't post your entity definitions, so you will need to interpret the code in this answer to match up with your actual models. Also, note that querying the entities themselves, in this case, has nothing to do whether the data in the underlying tables is 'read-only' or not:
final String queryStr = "SELECT p FROM Person p WHERE p.civility.value = :value";
final TypedQuery<Person> query = entityManager.createQuery(queryStr, Person.class);
query.setParameter("value", "Mr");
List<Person> results = query.getResultList();