Being new to Spring Batch, I am making a good progress by exploring some POCs. One of the POCs I am currently working on is about reading data from csv file and loading into database tables which have parent child relationships.
CSV file now contains customers and orders info as below.
custname1, custaddress1, custzip1, orderdate11, ordervalue11, ordertax11
custname1, custaddress1, custzip1, orderdate12, ordervalue12, ordertax12
custname2, custaddress2, custzip2, orderdate21, ordervalue21, ordertax21
custname2, custaddress2, custzip2, orderdate22, ordervalue22, ordertax22
These data will be loaded into DB tables Customer, Order where CustId and OrderId will be auto generated by Oracle Sequences and table Order has CustId as a foreign key for maintaining 1 to many relationship ( 1 Customer can have multiple Orders )
FlatFileItemReader, FieldSetMapper, ItemWriter are the interfaces I am employing in this POC and I am using JdbcTemplate for DB operations.
I have following questions. Please clarify.
1) How to design my Reader bean?
The model class Customer has its members, getters and setters methods. Same with model class Order.
Should Customer class ( reading data from CSV file ) contain a member for Order class and corresponding getter, setter methods in order to represent Parent - Child Relationship?
2) How to retrieve primary key which I am currently creating it within the INSERT sql for Customer using sequence.nextval?
3) How to store all the Primary Keys from Parent data bulk load and then use them for foreign key in Child table data load ?
Thanks.
Consider persisting the data as Hibernate entities for this case. If you still want to use JdbcTemplate, here are some suggestions. This is a good example that i found for this - http://www.javaworld.com/article/2458888/spring-framework/open-source-java-projects-spring-batch.html
1) How to design my Reader bean?
The reader should just read the given given lines into a corresponding single bean (lets call it CustomerOrder). In the reader do not try to map to a parent-child bean structure.
2) How to retrieve primary key which I am currently creating it within
the INSERT sql for Customer using sequence.nextval?
Use SimpleJdbcInsert. In the ItemWriter, for each CustomerOrder bean, check if the Customer name is already there (I am assuming each Customer has unique name and address). Check this sample (note I have not executed/compiled it. It is just an illustration)
public class CustomerOrderWriter implements ItemWriter<CustomerOrder>
{
private static final String GET_CUSTOMER = "select * from CUSTOMER where name = ? and address =?";
private static final String INSERT_CUSTOMER = "insert into CUSTOMER (name,address) values (?,?)";
private static final String INSERT_ORDER = "insert into ORDER (customer_id, date, value, tax) values (?,?,?,?)";
#Autowired
private JdbcTemplate jdbcTemplate;
#Override
public void write(List<? extends CustomerOrder> customerOrders) throws Exception
{
//Process Write for each CSV row
for( CustomerOrder custOrd : customerOrders )
{
//Check if a customer exists
List<Customer> custList = jdbcTemplate.query(GET_CUSTOMER, new Object[] {custOrd.getName(), custOrd.getAddress() }, new RowMapper<Customer>() {
#Override
public Customer mapRow( ResultSet resultSet, int rowNum ) throws SQLException {
Customer c = new Customer ();
c.setId( resultSet.getInt( 1 ) );
return c;
}
});
//If customer already exists, just get the ID and insert the Order
if( custList.size() > 0 )
{
//Since the customer is found, insert the Order table here with the customer ID.
}
else
{
//Insert the customer, using "SimpleJdbcInsert", get the newly inserted customer Id and then insert the Order table
}
}
}
}
3) How to store all the Primary Keys from Parent data bulk load and
then use them for foreign key in Child table data load ?
If you follow design in answer 2, you don't need to do this.
Related
I have a lot of (like 60+) tables which have the same schema and similar names:
log_2020_07_01
log_2020_07_02
... and so on.
They have the same columns: id, site, size. Each of the table contains around 2 million rows.
I've read Hibernate and tables with same data/columns but with different table names which suggests to use hibernate alone. I hope after seven years maybe there's something new we could do with JPA.
In JPA, is it possible to just write one entity class and let the code to handle which table to use?
E.G.,
for(int i=0;i<60;i++) {
// read from the i-th table.
}
First of all, we could use a middleware to make the sharding transparent to users for middle/large projects.
Here is a quick work around for my small project (and I'm the only developer working it):
Step 1, create an inteceptor:
public class MySqlInterceptor extends EmptyInterceptor {
private String entityName;
#Setter
private int tableId;
protected MySqlInterceptor() {
super();
}
public MySqlInterceptor(String entityName) {
this.entityName = entityName;
}
#Override
public String onPrepareStatement(String sql) {
// Here is the trick.
String modifiedSql = sql.replaceAll(entityName, entityName + tableId);
log.debug("{}", modifiedSql);
return modifiedSql;
}
}
Step 2, hock up the interceptor:
MySqlInterceptor mySqlInterceptor = new MySqlInterceptor("temp");
mySqlInterceptor.setTableId(tableId);
session = entityManagerFactory.unwrap(SessionFactory.class).withOptions().interceptor(mySqlInterceptor).openSession();
Explanation:
Hibernate is using JDBC to communicate with the database. The interceptor will change the table name in the sql from an entity's name (in my case it's temp) to a real table name (temp1, temp2, ...) at runtime.
P.S., be careful when you use multi-thread.
I'm implementing the PUT Methods for a REST-API.
I have a POJO similar to the following:
public class Brand implements Serializable {
#Column(columnDefinition = "serial")
#Generated(GenerationTime.INSERT)
#JsonIgnore
private Integer id;
#Id
#JsonProperty("brand")
private String brand;
.
.
.
}
Within the postgresql database the brand table has the following columns:
a database-internal id (SERIAL) which shall not be visible to the outside.(This is used manly for joining tables)
a brand (TEXT) which is the primary key
My Service method looks like this:
public Brand updateBrand(String brand, Brand update) {
Brand b = brandRepository.findBrandByBrand(brand);
if(b == null) { //If not exists create new one
b = new Brand(null, brand);
}
else { //If exists keep id, delete old one and create new entry
if(update != null && update.getBrand() != null) {
brandRepository.delete(b);
}
ServiceUtils.copyProperties(update, b); //This is within the if clause, because brand is the only value
}
return brandRepository.save(b);
}
And the controller would have something like this:
#PutMapping(value = "/brand/{brand}")
public ResponseEntity<Brand> updateBrand(#PathVariable("brand") String brand,
#RequestBody Brand update) {
Brand updated = articleNumberService.updateBrand(brand, update);
if(updated == null) {
throw new EntryCreationFailedException(brand); //self made exception
}
return new ResponseEntity<>(updated, HttpStatus.OK);
}
Now my following problem is, that when calling PUT ../brand/stackoverflow
with body:
{"brand":"StackOverflow")
it deletes the old stackoverflow brand (which had id=1, for example) and creates a new one called StackOverflow. But when checking the database the id column is incremented (so now it has id=2).
I checked and this is caused by hibernate still calling:
insert
into
brand
(brand)
values
(?)
This definitly is what I want when id is null. Which happens when creating a new Brand for example. But when only overriding the brand and id is not null I want hibernate to call this:
insert
into
brand
(id, brand)
values
(?, ?)
I know this would be possible by creating a own save method and in an emergency override the query. BUT I'm quite optimistic that this should be possible without. Bt I can't realy find fitting answers to this. I already had problems finding the proper annotations for the postgresql-specific serial behavior.
P.S: I know that some will shout "why would you have Brand as the primary key and not id!?" But this only is a simple class/part of the database. There are more complex classes which use exactly the same way for the internal database id, (and actually need it), but have multiple primary keys etc. So this rather is a very simple representation for my problem.
Since the Postgres use the serial as the auto_increment, it's not included the id insertion in sql statement. Behind the scene, it create the sequence for it. So you should use the Generation type IDENTITY for it.
If it was a field with #Id you would be able to write a custom #GenericGenerator that fetched the sequence if the value was null, but as it's not a primary key, I think you would end up having to have a separate parent Entity with its own generated Id.
I am setting up criteria for database calls and am having trouble getting understanding how to set up my code. I have put my criteria in and now need to know how I make sure I populate the variables right. This is what I have.
Public class Key extends abstractDAO<key>{
Public List<Key> getKeyValues(){
Criteria c = createCriteria();
c.add(Restrictions.lt("id", 3)).addOrder(Order.asc("id")).list();
return c.list();
Now the table has 2 rows. One is current and the second is a new request row. The database has 3 columns. Column 1 is the I'd, column two is the key, and 3 is a timestamp. I need to populate all the variables in order. I am not sure how to go about this in my key.java file
Key.java
#column(name="id")
private int actualID;
#column(name="key")
private Boolean actualKey;
#column (name="actualTime")
private Date actualTime;
Then repeats with requested, requestKey, and requestTime. Then I have public get and sets for each. Have not added any parameters to any method yet either. I am not sure how to set this file up so the list actually sets the variables when the DAO request the rows of the database.
try the following:
public List<Key> getKeyValues()
{
return createCriteria()
.add( Restrictions.lt( "actualID", 3 ) )
.addOrder( Order.asc( "actualID" ) )
.list();
}
Provided that the rest of the mapping and the implementation of createCriteria() are correct, it should return a list of Key's objects whose ids are less than 3, ordered by id.
I am creating an application with database access. I do not use JPA nor JooQ or other frameworks for reasons (and do not want to. Also this is not important for my question). So I use JDBC and write plain sql.
My model looks like this:
public class Contact{
AddressBook addressBok;
String name;
...
}
I now created 2 DAOs, one for Contact and AddressBook. My Contact table has a foreign key to the AddressBook table (address_book_id).
I have a Service Class (e.g. ContactService) which would read each object using the corrsponding DAO and combine it to one Contact.
The Problem now is: I have the address_book_id in the ResultSet in the ContactDAO. How do I pass it over to the service class which then reads the correspondig AddressBook using the AddressBookDAO? As the model is shared, it is not a good solution to put a String addressBookId to Contact as clients using this model may do not know anything about the database.
I know these questions, but there is no answer on how to do it:
Using DAOs with composite Objects
Can a DAO call DAO?
The best practice is to use POJO domain object per each table where you can save your relationship fields like address_book_id. So you will have tree independent classes Contact, Address, AddressBook and independent DAO ContractDAO, AddressDAO, AddressBookDAO. Your ContactService will manipulate with these 6 objects to load, save, modify related data.
you can use the decorator pattern to wrap the entity(model class) that you want to add a foreign key to it, like this:
how to use:
for example: save(insert into db) a contact (here where I found the problem last time, so i fixed it by this solution)--> problem:how to insert a contact without knowing the fk address_book_id, in the args of save
public class ContactDAO extends DAO<Contact>{
// ....
#Override
public int save(IEntity contact){
ForeignKeyWrapper ctFk = (ForeignKeyWrapper)contact;
int address_book_id = ctFk.getFK();
Contact ct = (Contact)ctFk.getEntity();
String name = ct.getName();
// retrieve other fields of contact here
//use some sql to insert the contact in the db, now you have also the fk
String insertQuery = "INSERT INTO CONTACT(name,..other fields of contact.. , address_book_id) VALUES("+
name + "," +
/*other fields*/
address_book_id + ")";
//execute it here and fetch the id of the inserted row
}
// ....
}
public class clientClass{
//....
public static void main(String[] args) {
IEntity contact = new Contact(/* init fields*/);
IEntity addressBook = new AddressBook(/* init fields*/);
ForeignKeyWrapper ctFKW = new ForeignKeyWrapper(contact);
//link contact to addressBook (e.g: when creating a contact to insert it into the db)
ctFKW.setFK(addressBook.getId());
ContactDAO ctDAO = new ContactDAO(/*connection*/);
ctDAO.save(ctFKW);
}
//....
}
you can add a builder class to build your contact objects and link their foreign keys to addressBook objects Ids, to encapsulate the building logic, and simplify the process
Scenario: Hibernate 3.6 with xml-based mapping, Java7, Postgresql 8.3.
I'm currently refactoring an application where I have got this scenario for the database:
main_table
id integer
other_field string
(id) PK
secondary_table
other_field string
value string
(other_field, value) PK
Basically, there's a secondary table which contains an "other_field" which is matched on the main table; I need to extract all values for a certain record in main_table and map them.
In SQL I'd use a query like:
SELECT value FROM secondary table INNER JOIN main_table ON secondary_table.other_field == main_table.other_field where main_table.id = 1;
But I don't understand how to map a collection of basic types (strings) to the Main object in Java using such a query (or a similar one if the one I propose is not hibernate friendly), so that I can have a "values" property on my mapped object, which should be a Set<String>
I think this is what you are looking for:
#Entity
public class Primary { // Main table
#Id
#Column(name="EMP_ID")
private long id;
...
#ElementCollection
#CollectionTable(
name="PRIMARY_SECONDARY",
joinColumns=#JoinColumn(name="PRIMARY_ID")
)
private Set<Secondary> phones;
...
}
#Embeddable
public class Secondary { // Secondary table
private String value;
...
}
Full example and further details.