Is it possible to use GenerationType.IDENTITY with Transaction in Hibernate/Spring data?
I have an existing database, with an identity column in all tables. So, I have to use GenerationType.IDENTITY for it. But, when I create a new entity instance and change its state to managed with someRepository.save(...) method, the persistence provider can't acquire a new ID for that entity, because it must happen on flush time, at the end of the transaction.
If I create one entity, all works as expected. After save(), the entity goes to the managed state, the id is changed from NULL to 0 (zero), and at the flush time, the new ID for the row is generated by the database.
But what if we create two instances of the same entity class inside one transaction? The exception will be thrown, and this is justly because we have two different objects with same ID = 0. So, is there a way to deal with it without change strategy from IDENTITY to something else?
#Entity
public class Customer {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
Long id;
...
}
#Transactional
public void brokenCode() {
Customer one = new Customer();
Customer two = new Customer();
someRepository.save(one);
someRepository.save(two); <--- org.springframework.dao.DataIntegrityViolationException: A different object with the same identifier value was already associated with the session
}
CREATE TABLE [dbo].[Customer]
(
[id] [int] IDENTITY (1,1) NOT NULL,
...
CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED ([id] ASC)
WITH (PAD_INDEX = OFF
, STATISTICS_NORECOMPUTE = OFF
, IGNORE_DUP_KEY = OFF
, ALLOW_ROW_LOCKS = ON
, ALLOW_PAGE_LOCKS = ON
, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [DATA]
)
UPD!
The reason is the table has "trigger instead of insert". In that trigger some fields are calculated based on other fields. And the identity doesn't return to Hibernate.
ALTER trigger [dbo].[Customer] on [dbo].[Customer]
instead of insert
as
if ##rowcount = 0 return
set nocount on
Set dateformat dmy
Insert into dbo.Customer
(f1, f2, ....)
Select
I.f1,
isnull(f2,newid()),
...
from Inserted I
So. Is there a way to somehow return identity to Hibernate from that trigger?
Thanks in advance!
This question already has answers here:
PreparedStatement IN clause alternatives?
(33 answers)
Closed 5 years ago.
Say that I have a query of the form
SELECT * FROM MYTABLE WHERE MYCOL in (?)
And I want to parameterize the arguments to in.
Is there a straightforward way to do this in Java with JDBC, in a way that could work on multiple databases without modifying the SQL itself?
The closest question I've found had to do with C#, I'm wondering if there is something different for Java/JDBC.
There's indeed no straightforward way to do this in JDBC. Some JDBC drivers seem to support PreparedStatement#setArray() on the IN clause. I am only not sure which ones that are.
You could just use a helper method with String#join() and Collections#nCopies() to generate the placeholders for IN clause and another helper method to set all the values in a loop with PreparedStatement#setObject().
public static String preparePlaceHolders(int length) {
return String.join(",", Collections.nCopies(length, "?"));
}
public static void setValues(PreparedStatement preparedStatement, Object... values) throws SQLException {
for (int i = 0; i < values.length; i++) {
preparedStatement.setObject(i + 1, values[i]);
}
}
Here's how you could use it:
private static final String SQL_FIND = "SELECT id, name, value FROM entity WHERE id IN (%s)";
public List<Entity> find(Set<Long> ids) throws SQLException {
List<Entity> entities = new ArrayList<Entity>();
String sql = String.format(SQL_FIND, preparePlaceHolders(ids.size()));
try (
Connection connection = dataSource.getConnection();
PreparedStatement statement = connection.prepareStatement(sql);
) {
setValues(statement, ids.toArray());
try (ResultSet resultSet = statement.executeQuery()) {
while (resultSet.next()) {
entities.add(map(resultSet));
}
}
}
return entities;
}
private static Entity map(ResultSet resultSet) throws SQLException {
Enitity entity = new Entity();
entity.setId(resultSet.getLong("id"));
entity.setName(resultSet.getString("name"));
entity.setValue(resultSet.getInt("value"));
return entity;
}
Note that some databases have a limit of allowable amount of values in the IN clause. Oracle for example has this limit on 1000 items.
Since nobody answer the case for a large IN clause (more than 100) I'll throw my solution to this problem which works nicely for JDBC. In short I replace the IN with a INNER JOIN on a tmp table.
What I do is make what I call a batch ids table and depending on the RDBMS I may make that a tmp table or in memory table.
The table has two columns. One column with the id from the IN Clause and another column with a batch id that I generate on the fly.
SELECT * FROM MYTABLE M INNER JOIN IDTABLE T ON T.MYCOL = M.MYCOL WHERE T.BATCH = ?
Before you select you shove your ids into the table with a given batch id.
Then you just replace your original queries IN clause with a INNER JOIN matching on your ids table WHERE batch_id equals your current batch. After your done your delete the entries for you batch.
The standard way to do this is (if you are using Spring JDBC) is to use the org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate class.
Using this class, it is possible to define a List as your SQL parameter and use the NamedParameterJdbcTemplate to replace a named parameter. For example:
public List<MyObject> getDatabaseObjects(List<String> params) {
NamedParameterJdbcTemplate jdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
String sql = "select * from my_table where my_col in (:params)";
List<MyObject> result = jdbcTemplate.query(sql, Collections.singletonMap("params", params), myRowMapper);
return result;
}
I solved this by constructing the SQL string with as many ? as I have values to look for.
SELECT * FROM MYTABLE WHERE MYCOL in (?,?,?,?)
First I searched for an array type I can pass into the statement, but all JDBC array types are vendor specific. So I stayed with the multiple ?.
I got the answer from docs.spring(19.7.3)
The SQL standard allows for selecting rows based on an expression that includes a variable list of values. A typical example would be select * from T_ACTOR where id in (1, 2, 3). This variable list is not directly supported for prepared statements by the JDBC standard; you cannot declare a variable number of placeholders. You need a number of variations with the desired number of placeholders prepared, or you need to generate the SQL string dynamically once you know how many placeholders are required. The named parameter support provided in the NamedParameterJdbcTemplate and JdbcTemplate takes the latter approach. Pass in the values as a java.util.List of primitive objects. This list will be used to insert the required placeholders and pass in the values during the statement execution.
Hope this can help you.
AFAIK, there is no standard support in JDBC for handling Collections as parameters. It would be great if you could just pass in a List and that would be expanded.
Spring's JDBC access supports passing collections as parameters. You could look at how this is done for inspiration on coding this securely.
See Auto-expanding collections as JDBC parameters
(The article first discusses Hibernate, then goes on to discuss JDBC.)
See my trial and It success,It is said that the list size has potential limitation.
List l = Arrays.asList(new Integer[]{12496,12497,12498,12499});
Map param = Collections.singletonMap("goodsid",l);
NamedParameterJdbcTemplate namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(getJdbcTemplate().getDataSource());
String sql = "SELECT bg.goodsid FROM beiker_goods bg WHERE bg.goodsid in(:goodsid)";
List<Long> list = namedParameterJdbcTemplate.queryForList(sql, param2, Long.class);
There are different alternative approaches that we can use.
Execute Single Queries - slow and not recommended
Using Stored Procedure - database specific
Creating PreparedStatement Query dynamically - good performance but loose benefits of caching and needs recompilation
Using NULL in PreparedStatement Query - I think this is a good approach with optimal performance.
Check more details about these here.
sormula makes this simple (see Example 4):
ArrayList<Integer> partNumbers = new ArrayList<Integer>();
partNumbers.add(999);
partNumbers.add(777);
partNumbers.add(1234);
// set up
Database database = new Database(getConnection());
Table<Inventory> inventoryTable = database.getTable(Inventory.class);
// select operation for list "...WHERE PARTNUMBER IN (?, ?, ?)..."
for (Inventory inventory: inventoryTable.
selectAllWhere("partNumberIn", partNumbers))
{
System.out.println(inventory.getPartNumber());
}
One way i can think of is to use the java.sql.PreparedStatement and a bit of jury rigging
PreparedStatement preparedStmt = conn.prepareStatement("SELECT * FROM MYTABLE WHERE MYCOL in (?)");
... and then ...
preparedStmt.setString(1, [your stringged params]);
http://java.sun.com/docs/books/tutorial/jdbc/basics/prepared.html
Why is my persistent object returning transient objects when fetching via a relationship?
ObjectContext context = BaseContext.getThreadObjectContext();
// Delete some employee schedules
List<EmployeeSchedule> employeeSchedules = this.getEmployeeSchedules();
for (EmployeeSchedule employeeSchedule : employeeSchedules) {
context.deleteObject(employeeSchedule);
}
// Add new schedules
for(int i = 0; i < someCondition; i++) {
EmployeeSchedule employeeSchedule = context.newObject(EmployeeSchedule.class);
addToEmployeeSchedules(employeeSchedule);
}
context.commitChanges();
List<EmployeeSchedule> es = getEmployeeSchedules(); // returns transient objects
It is inserting the data correctly into the database. Would this be an issue with stale data in the cache?
I'm answering my own question in case someone else get tripped up by this in the future.
I have a many-to-many relationship.
Employee - EmployeeSchedule - Schedule
According to the delete rules here: http://cayenne.apache.org/docs/3.0/delete-rules.html, I set the fields employee_id and schedule_id in the EmployeeSchedule to Nullify rule on delete.
I also had to configure the join table EmployeeSchedule by making employee_id and schedule_id primary keys in the Modeler and checking the "to Dep PK" checkbox in the employee and schedule dbEntity.
Relevant links: http://objectstyle.org/cayenne/lists/cayenne-user/2004/02/0017.html
http://grokbase.com/t/cayenne/user/085d70sysk/to-dep-checkbox-was-one-to-many-problem
I have multiple tables with a lot of data in it. I'm only mentioning things pertinent to the question. For rendering data on the jsp, I only need selective data from different tables.
In reference to travel-vacation, lets say I have to retrieve and render holiday package name, itinerary duration and total price per defaults.
Holiday package name is in table HolidayPackage (id, name)
Itinerary duration in table Itinerary Itinerary (idPkg, idItinerary, dayNum) = (SUM aggregate of itinerary rows for a given package )
Price is defined in two different tables base price and total price
= (Sum( base(idPkg) + total(idPkg) ))
How can I retrieve selective data from these database tables to render the name, itinerary duration and total price for a given holiday package ?
I am using Hibernate with mysql as my database.
Thoughts:
Can I use a view to fetch data into my custom java object Result where Result is defined as:
class Result{
String name;
Intger itineraryDuration;
BigDecimal totalPrice;
}
Also, can I use functions as the definition of column values in views ?
What I do with views is I create an immutable hibernate model class and associated DAO to query the model class.
I annotate the model class as follows:
#Entity
#Immutable
#Table(name = "CLASSIFICATION_LABELS_V", schema = "CORPORATE")
I also set the setters to protected, but this is optional.
If the view is structured similar to an existing model class, you can use a named query and map it to your model using the resultClass attribute. The following is for Oracle so if using anything else, the query parameter would be different.
#NamedNativeQueries({
#NamedNativeQuery(name = "create_product_from_template", query = "call pkg_authoring.sp_create_prod_from_template(?, :productId, :templateId)", resultClass = TblProduct.class, callable = true)
})
In hibernate, you can use functions as follows:
#Formula(value = "(select fnHasProducts(customerId) from dual)")
#AccessType("property")
private Integer products = 0;
#Formula(value = "(select fnAssigneeForProduct(productId) from dual)")
private Integer assignee = 0;
I have an entity Venue with its Events:
Event { ID, Name, DateTime }
Venue { ID, Name, #OneToMany List<Event> events}
What I would like to achieve is to be able to call these functions in view (with OpenEntityManagerInView):
customVenue.getId(); // no problem
customVenue.getName(); // no problem
customVenue.getEvents(); // no problem
customVenue.getCurrentEvents(); // hm?
customVenue.getPastEvents(); // hm?
There might be thousands of events in the database, so iterating over the "events" to get the current ones might not be a good idead.
Is this a correct approach? Is this feasible? How can I split events in current and past and to have them ordered?
Thanks!
Of course it's possible. But not using a method of customVenue (unless the entity has a reference to the entity manager, which is a bad idea).
Execute a query:
String jpql = "select event from Venue venue"
+ " inner join venue.events event"
+ " where event.date < :now"
+ " order by event.date asc";
List<Event> pastEvents = em.createQuery(jpql, Event.class)
.setParameter("now", new Date())
.getResultList();