I've a question on hibernate operation: update.
Here a bit of code:
Campaign campaign = campaignDAO.get(id);
campaign.setStatus(true);
campaignDAO.update(campaign);
If I just have all the data of the campaign object, is there any way to perform an update without perform the first select (campaignDAO.get(id)) ?
Thanks,
Alessio
HQL will definitely help you.
In order to maintain the separation of concerns, you can add a more specialized method in you DAO object:
public void updateStatusForId(long id, boolean status){
//provided you obtain a reference to your session object
session.createQuery("UPDATE Campaign SET status = " + status + " WHERE id = :id").setParameter("id", id).executeUpdate();
//flush your session
}
Then you could simply call this method from your business method. You can check the generated SQL statements inside the logs of your app by setting the show_sql hibernate property to true.
You can use session.load(). It will not hit the database. Here you can find its details and example code.
I had worte a extension to solve this issue in Nhibernate
how to use!
first of all you need enable dynamic-update="true"
using (ISession session = sessionFactory.OpenSession())
{
Customer c1 = new Customer();
c1.CustomerID = c.CustomerID;
session.Mark(c1);
// c1.Name = DateTime.Now.ToString();
c1.Phone = DateTime.Now.ToString();
//需要开启动态更新
session.UpdateDirty(c1);
session.Flush();
}
UpdateExtension.cs
public static class UpdateExtension
{
static readonly Object NOTNULL = new Object();
public static void UpdateDirty<TEntity>(this ISession session, TEntity entity)
{
SessionImpl implementor = session as SessionImpl;
EntityEntry entry = implementor.PersistenceContext.GetEntry(entity);
if (entry == null)
{
throw new InvalidOperationException("找不到对应的实例,请先使用Mask方法标记");
}
IEntityPersister persister = entry.Persister;
// 如果某列不可以为空,新的Entity里也不想更新他。
// 那么LoadState 里的值应该和Entity 中的值相同
Object[] CurrentState = entry.Persister.GetPropertyValues(entity, EntityMode.Poco);
Object[] LoadedState = entry.LoadedState;
int[] dirtys = persister.FindDirty(CurrentState
, LoadedState
, entity
, (SessionImpl)session);
if (dirtys == null || dirtys.Length == 0)
{
return;
}
persister.Update(entry.Id
, CurrentState
, dirtys
, true
, LoadedState
, entry.Version
, entity
, entry.RowId
, (SessionImpl)session);
implementor.PersistenceContext.RemoveEntry(entity);
implementor.PersistenceContext.RemoveEntity(entry.EntityKey);
session.Lock(entity, LockMode.None);
// 防止(implementor.PersistenceContext.EntityEntries.Count == 0)
}
public static void Mark<TEntity>(this ISession session, TEntity entity)
{
session.Lock(entity, LockMode.None);
}
}
here is update sql
command 0:UPDATE Customers SET Phone = #p0 WHERE CustomerID = #p1;#p0 = '2014/12/26 0:12:56' [Type: String (4000)], #p1 = 1 [Type: Int32 (0)]
Only update Phone column .
event Name property can not be null. we can work very well.
Related
I can't set deptID value which is a foreign key I tried many ways, but it doesn't work.
I am getting the values from HTML form.
mapping table units
public BigDecimal getUnitId() {
return this.unitId;
}
public void setUnitId(BigDecimal unitId) {
this.unitId = unitId;
}
public Depts getDepts() { //deptID column - FK
return this.depts;
}
public void setDepts(Depts depts) {
this.depts = depts;
}
my code
<%
SessionFactory sf= new Configuration().configure().buildSessionFactory();
Session s= sf.openSession();
Transaction t= s.beginTransaction();
String unitID= request.getParameter("unitID");
String deptID= request.getParameter("deptID");
String unitName= request.getParameter("unitName");
Units r= new Units(); //this code to set deptID
Depts y= new Depts();
r.setUnitId(new BigDecimal(unitID));
//r.setDepts(y.setDeptId(new BigDecimal(deptID)));//here is my problem
y.setDeptId(new BigDecimal(deptID));
r.setUnitName(unitName);
s.save(r);
t.commit();
out.println("done enserting");
%>
I could suspect the error by looking at your code that you're trying to save the value which you're receiving from your html page, instead of saving the object of your Department (or your class name which has deptID primary-key) class.
In order to save the deptID along with your record :-
you need to get the object of that class first. Something like this :-
Units r = new Units();
YourDepartmentClass d = getDeparmentRecord(id) //get the `YourDepartmentClass` class object on the basis of the id you're receiving
r.setDepId(d); //set/associate YourDepartmentClass object in your Unit's object
//save the record
I have a Java Spring based web application and I want to insert a record to a table only if the table does not contain any rows that are "similar" (according to some specific, irrelevant criteria) to the new row.
Because this is a multi-threaded environment, I cannot use a SELECT+INSERT two-step combination as it would expose me to a race condition.
The same question was first asked and answered here and here several years ago. Unfortunately, the questions have got only a little attention and the provided answer is not sufficient to my needs.
Here's the code I currently have and it's not working:
#Component("userActionsManager")
#Transactional
public class UserActionsManager implements UserActionsManagerInterface {
#PersistenceContext(unitName = "itsadDB")
private EntityManager manager;
#Resource(name = "databaseManager")
private DB db;
...
#SuppressWarnings("unchecked")
#Override
#PreAuthorize("hasRole('ROLE_USER') && #username == authentication.name")
public String giveAnswer(String username, String courseCode, String missionName, String taskCode, String answer) {
...
List<Submission> submissions = getAllCorrectSubmissions(newSubmission);
List<Result> results = getAllCorrectResults(result);
if (submissions.size() > 0
|| results.size() > 0) throw new SessionAuthenticationException("foo");
manager.persist(newSubmission);
manager.persist(result);
submissions = getAllCorrectSubmissions(newSubmission);
results = getAllCorrectResults(result);
for (Submission s : submissions) manager.lock(s, LockModeType.OPTIMISTIC_FORCE_INCREMENT);
for (Result r : results ) manager.lock(r, LockModeType.OPTIMISTIC_FORCE_INCREMENT);
manager.flush();
...
}
#SuppressWarnings("unchecked")
private List<Submission> getAllCorrectSubmissions(Submission newSubmission) {
Query q = manager.createQuery("SELECT s FROM Submission AS s WHERE s.missionTask = ?1 AND s.course = ?2 AND s.user = ?3 AND s.correct = true");
q.setParameter(1, newSubmission.getMissionTask());
q.setParameter(2, newSubmission.getCourse());
q.setParameter(3, newSubmission.getUser());
return (List<Submission>) q.getResultList();
}
#SuppressWarnings("unchecked")
private List<Result> getAllCorrectResults(Result result) {
Query q = manager.createQuery("SELECT r FROM Result AS r WHERE r.missionTask = ?1 AND r.course = ?2 AND r.user = ?3");
q.setParameter(1, result.getMissionTask());
q.setParameter(2, result.getCourse());
q.setParameter(3, result.getUser());
return (List<Result>) q.getResultList();
}
...
}
According to the answer provided here I am supposed to somehow use OPTIMISTIC_FORCE_INCREMENT but it's not working. I suspect that the provided answer is erroneous so I need a better one.
edit:
Added more context related code. Right now this code still has a race condition. When I make 10 simultaneous HTTP POST requests approximately 5 rows will get erroneously inserted. Other 5 requests are rejected with HTTP error code 409 (conflict). The correct code would guarantee that only 1 row would get inserted to the database no matter how many concurrent requests I make. Making the method synchronous is not a solution since the race condition still manifests for some unknown reason (I tested it).
Unfortunately after several days of research I was unable to find a short and simple solution to my problem. Since my time budget is not unlimited I had to come up with a workaround. Call it a kludge if you may.
Since the whole HTTP request is a transaction, it will be rolled back at the sight of any conflicts. I am using this for my advantage by locking a special entity within the context of the whole HTTP request. Should multiple HTTP requests be received at the same time, all but one will result in some PersistenceException.
In the beginning of the transaction I am checking whether no other correct answers have been submitted yet. During that check the lock is already effective so no race condition could happen. The lock is effective until the answer is submitted. This basically simulates a critical section as a SELECT+INSERT two step query on the application level (in pure MySQL I would have used the INSERT IF NOT EXISTS construct).
This approach has some drawbacks. Whenever two students submit an answer at the same time, one of them will be thrown an exception. This is sort of bad for performance and bandwidth because the student who received HTTP STATUS 409 has to resubmit their answer.
To compensate the latter, I am automatically retrying to submit the answer on the server side a couple of times between randomly chosen time intervals. See the according HTTP request controller code is below:
#Controller
#RequestMapping("/users")
public class UserActionsController {
#Autowired
private SessionRegistry sessionRegistry;
#Autowired
#Qualifier("authenticationManager")
private AuthenticationManager authenticationManager;
#Resource(name = "userActionsManager")
private UserActionsManagerInterface userManager;
#Resource(name = "databaseManager")
private DB db;
.
.
.
#RequestMapping(value = "/{username}/{courseCode}/missions/{missionName}/tasks/{taskCode}/submitAnswer", method = RequestMethod.POST)
public #ResponseBody
Map<String, Object> giveAnswer(#PathVariable String username,
#PathVariable String courseCode, #PathVariable String missionName,
#PathVariable String taskCode, #RequestParam("answer") String answer, HttpServletRequest request) {
init(request);
db.log("Submitting an answer to task `"+taskCode+"` of mission `"+missionName+
"` in course `"+courseCode+"` as student `"+username+"`.");
String str = null;
boolean conflict = true;
for (int i=0; i<10; i++) {
Random rand = new Random();
int ms = rand.nextInt(1000);
try {
str = userManager.giveAnswer(username, courseCode, missionName, taskCode, answer);
conflict = false;
break;
}
catch (EntityExistsException e) {throw new EntityExistsException();}
catch (PersistenceException e) {}
catch (UnexpectedRollbackException e) {}
try {
Thread.sleep(ms);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
if (conflict) str = userManager.giveAnswer(username, courseCode, missionName, taskCode, answer);
if (str == null) db.log("Answer accepted: `"+answer+"`.");
else db.log("Answer rejected: `"+answer+"`.");
Map<String, Object> hm = new HashMap<String, Object>();
hm.put("success", str == null);
hm.put("message", str);
return hm;
}
}
If for some reason the controller is unable to commit the transaction 10 times in a row then it will try one more time but will not attempt to catch the possible exceptions. When an exception is thrown on the 11th try then it will be processed by the global exception controller and the client will receive HTTP STATUS 409. The global exception controller is defined below.
#ControllerAdvice
public class GlobalExceptionController {
#Resource(name = "staticDatabaseManager")
private StaticDB db;
#ExceptionHandler(SessionAuthenticationException.class)
#ResponseStatus(value=HttpStatus.FORBIDDEN, reason="session has expired") //403
public ModelAndView expiredException(HttpServletRequest request, Exception e) {
ModelAndView mav = new ModelAndView("exception");
mav.addObject("name", e.getClass().getSimpleName());
mav.addObject("message", e.getMessage());
return mav;
}
#ExceptionHandler({UnexpectedRollbackException.class,
EntityExistsException.class,
OptimisticLockException.class,
PersistenceException.class})
#ResponseStatus(value=HttpStatus.CONFLICT, reason="conflicting requests") //409
public ModelAndView conflictException(HttpServletRequest request, Exception e) {
ModelAndView mav = new ModelAndView("exception");
mav.addObject("name", e.getClass().getSimpleName());
mav.addObject("message", e.getMessage());
synchronized (db) {
db.setUserInfo(request);
db.log("Conflicting "+request.getMethod()+" request to "+request.getRequestURI()+" ("+e.getClass().getSimpleName()+").", Log.LVL_SECURITY);
}
return mav;
}
//ResponseEntity<String> customHandler(Exception ex) {
// return new ResponseEntity<String>("Conflicting requests, try again.", HttpStatus.CONFLICT);
//}
}
Finally, the giveAnswer method itself utilizes a special entity with a primary key lock_addCorrectAnswer. I lock that special entity with the OPTIMISTIC_FORCE_INCREMENT flag which makes sure that no two transactions can have overlapping execution times for the giveAnswer method. The respective code can be seen below:
#Component("userActionsManager")
#Transactional
public class UserActionsManager implements UserActionsManagerInterface {
#PersistenceContext(unitName = "itsadDB")
private EntityManager manager;
#Resource(name = "databaseManager")
private DB db;
.
.
.
#SuppressWarnings("unchecked")
#Override
#PreAuthorize("hasRole('ROLE_USER') && #username == authentication.name")
public String giveAnswer(String username, String courseCode, String missionName, String taskCode, String answer) {
.
.
.
if (!userCanGiveAnswer(user, course, missionTask)) {
error = "It is forbidden to submit an answer to this task.";
db.log(error, Log.LVL_MAJOR);
return error;
}
.
.
.
if (correctAnswer) {
.
.
.
addCorrectAnswer(newSubmission, result);
return null;
}
newSubmission = new Submission(user, course, missionTask, answer, false);
manager.persist(newSubmission);
return error;
}
private void addCorrectAnswer(Submission submission, Result result) {
String var = "lock_addCorrectAnswer";
Global global = manager.find(Global.class, var);
if (global == null) {
global = new Global(var, 0);
manager.persist(global);
manager.flush();
}
manager.lock(global, LockModeType.OPTIMISTIC_FORCE_INCREMENT);
manager.persist(submission);
manager.persist(result);
manager.flush();
long submissions = getCorrectSubmissionCount(submission);
long results = getResultCount(result);
if (submissions > 1 || results > 1) throw new EntityExistsException();
}
private long getCorrectSubmissionCount(Submission newSubmission) {
Query q = manager.createQuery("SELECT count(s) FROM Submission AS s WHERE s.missionTask = ?1 AND s.course = ?2 AND s.user = ?3 AND s.correct = true");
q.setParameter(1, newSubmission.getMissionTask());
q.setParameter(2, newSubmission.getCourse());
q.setParameter(3, newSubmission.getUser());
return (Long) q.getSingleResult();
}
private long getResultCount(Result result) {
Query q = manager.createQuery("SELECT count(r) FROM Result AS r WHERE r.missionTask = ?1 AND r.course = ?2 AND r.user = ?3");
q.setParameter(1, result.getMissionTask());
q.setParameter(2, result.getCourse());
q.setParameter(3, result.getUser());
return (Long) q.getSingleResult();
}
}
It is important to note that the entity Global has to have a version annotation in its class for the OPTIMISTIC_FORCE_INCREMENT to work (see code below).
#Entity
#Table(name = "GLOBALS")
public class Global implements Serializable {
.
.
.
#Id
#Column(name = "NAME", length = 32)
private String key;
#Column(name = "INTVAL")
private int intVal;
#Column(name = "STRVAL", length = 4096)
private String strVal;
#Version
private Long version;
.
.
.
}
Such an approach can be optimized even further. Instead of using the same lock name lock_addCorrectAnswer for all giveAnswer calls, I could generate the lock name deterministically from the name of the submitting user. For example, if the student's username is Hyena then the primary key for the lock entity would be lock_Hyena_addCorrectAnswer. That way multiple students could submit answers at the same time without receiving any conflicts. However, if a malicious user spams the HTTP POST method for submitAnswer 10x in parallel they will be prevented by the this locking mechanism.
I have an android app in which I use greenDAO to model my database. I have an easy scenario but I don't understand how I can make it work. I've followed the documentation but I must be missing something.
I have 3 entities: User, Picture and Address. A User has Pictures and Addresses. My getters for Picture and Address always return null.
userEntity.getPicture(); -> returns null
userEntity.getAddress(); -> returns null
Here is my GreenDAO setup
Entity userEntity = schema.addEntity("User");
userEntity.addIdProperty();
userEntity.addStringProperty("firstName");
userEntity.addStringProperty("lastName");
Entity picture = schema.addEntity("Picture");
picture.addIdProperty();
picture.addByteArrayProperty("image");
picture.addStringProperty("imageName");
Entity address = schema.addEntity("Address");
address.addIdProperty();
address.addStringProperty("street");
address.addIntProperty("houseNumber");
address.addIntProperty("zipcode");
address.addStringProperty("city");
// a user can have multiple pictures but a picture is connected to one user
Property pictureIdProperty = picture.addLongProperty("userId").getProperty();
picture.addToOne(userEntity, pictureIdProperty).setName("user");
userEntity.addToMany(picture, pictureIdProperty).setName("picture");
// a user can have multiple addresses but an address is only connected to one user
Property addressIdProperty = address.addLongProperty("userId").getProperty();
address.addToOne(userEntity, addressIdProperty).setName("user");
userEntity.addToMany(address, addressIdProperty).setName("address");
Here is my testclass to test the relations
DevOpenHelper helper = new DaoMaster.DevOpenHelper(getApplication(), "relation_test_db", null);
SQLiteDatabase db = helper.getWritableDatabase();
DaoMaster daoMaster = new DaoMaster(db);
this.daoSession = daoMaster.newSession();
UserDao userDao = this.daoSession.getUserDao();
PictureDao pictureDao = this.daoSession.getPictureDao();
AddressDao addressDao = this.daoSession.getAddressDao();
// clear all data
userDao.deleteAll();
pictureDao.deleteAll();
addressDao.deleteAll();
/**
* create data
*/
User bill = new User(null);
bill.setFirstName("Bill");
bill.setLastName("Murray");
Picture billsPicture = new Picture(null);
billsPicture.setImage("BillsExamplePictureByteArray".getBytes());
billsPicture.setImageName("BillsPictureName");
Address billsAddress = new Address(null);
billsAddress.setStreet("BillsStreet");
billsAddress.setHouseNumber(42);
billsAddress.setZipcode(12345);
billsAddress.setCity("Wilmette");
billsPicture.setUser(bill);
billsAddress.setUser(bill);
userDao.insert(bill);
pictureDao.insert(billsPicture);
addressDao.insert(billsAddress);
User user = userDao.queryBuilder().list().get(0);
ArrayList<Picture> billsPictureList = (ArrayList<Picture>) user.getPicture();
ArrayList<Address> billsAddressList = (ArrayList<Address>) user.getAddress();
if (billsPictureList == null || billsPictureList.size() == 0) {
// contact Markus
Toast.makeText(this, "Contact Stackoverflow", Toast.LENGTH_SHORT).show();
return;
}
if (billsAddressList == null || billsAddressList.size() == 0) {
// contact Markus
Toast.makeText(this, "Contact Stackoverflow", Toast.LENGTH_SHORT).show();
return;
}
Emanuel,
I am facing some similar issues when trying to save objects with 1-to-1 relations.
After spending pretty enough time with greenDAO I have found, that all "relational" objects should have appropriate mapping IDs of their "parents" before being saved to DB.
So I may suggest, that if you take a look at setUser methods of your generated Picture and Address entities, you will see something like:
public void setUser(User user) {
synchronized (this) {
this.user = user;
userId = user == null ? null : user.getId();
user__resolvedKey = userId;
}
}
Crucial is userId = user == null ? null : user.getId();
There are race conditions, as your created User object will not get ID until it is actually saved to DB. And if it does not have ID, there is a chance, that setUser of its relational entities will not work normally.
In your case you may try to change save sequence to:
//1. Save user to DB, this will give it ID
userDao.insert(bill);
//2. Set user entity with ID to its relational entities
billsPicture.setUser(bill);
billsAddress.setUser(bill);
//3. Save relational entities
pictureDao.insert(billsPicture);
addressDao.insert(billsAddress);
Hope my answer will be helpful to you.
I'm currently working with some legacy code whereby transactions are rolled manually.
Consider the following (example for illustrative purposes only, ignore syntax/design issues):
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
private PlatformTransactionManager transactionManager;
private DefaultTransactionDefinition txDefRequired = new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRED);
private DefaultTransactionDefinition txDefRequiresNew new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
void createPosts(Map<String, String> posts){
TransactionStatus status = transactionManager.getTransaction(txDefRequiresNew)
try {
// posts Map is made up for username:posttext key pair
for (Map.Entry<String, String> entry : posts.entrySet()) {
// Construct online post
createPost(entry.getKey(), entry.getValue());
}
transactionManager.commit(status);
} finally {
if (!status.isCompleted()) {
transactionManager.rollback(status);
}
}
}
void createPost(String username, String text){
TransactionStatus status = transactionManager.getTransaction(txDefRequired)
// Construct online post
OnlinePost p = new OnlinePost(text);
User u = resolveUser(username);
p.setUser(u);
transactionManager.commit(status); // flush the session
}
User resolveUser(String username){
TransactionStatus status = transactionManager.getTransaction(txDefRequired)
User u = getUser(username);
if(u == null)
createUser(username);
return u;
transactionManager.commit(status); // flush the session
}
Would it be correct to say that in this particular flow, tx.commit() within the called methods, such as resolveUser(), will only serve to flush the session (flush-mode = auto) and return a new persistent entity with an artificial primary key generated and assigned to it so that you can use it later on in the same transaction?
No. A commit will invariably end the unit of work and push the updates to the database. A flush on the other hand, does not commit the changes to the db (as in commit them so that it's visible for everyone). You could technically rollback a flushed change.
https://stackoverflow.com/a/14626510/2231632 for a possible duplicate.
And what you're looking for with the artificial primary key, you could use the Session.flush. But don't commit the txn if you want to use that key in the same txn.
I want to insert data into a table using the following code
public User registerUser(String usr, String pwd) {
u=em.find(User.class,usr);
if(u!=null)
{
return null;
}
String query1 = "insert into users values('" + usr + "','" + pwd +"')";
Query q = em.createQuery(query1);
u=em.find(User.class,usr);
return u;
}
here 'u' is the object of User class and em is EntityManager.
I get this following exception:
Servlet.service() for servlet action threw exception
org.hibernate.hql.ast.QuerySyntaxException: expecting OPEN, found 'values' near line 1, column 19 [insert into users values('pawan','am')]
Try
public User registerUser(String usr, String pwd) {
u=em.find(User.class,usr);
if(u!=null)
{
return null;
}
//Now saving...
em.getTransaction().begin();
em.persist(u); //em.merge(u); for updates
em.getTransaction().commit();
em.close();
return u;
}
If the PK is Identity, it will be set automatically in your persisted class, if you are using auto generation strategy (thanks to David Victor).
Edit to #aman_novice comment:
set it in your class
//Do this BEFORE getTransaction/persist/commit
//Set names are just a example, change it to your class setters
u.setUsr(usr);
u.setPwd(pwd);
//Now you can persist or merge it, as i said in the first example
em.getTransaction().begin();
(...)
About #David Victor, sorry I forgot about that.
You're not using SQL but JPAQL, there is no field-based insert. You persist object rather than inserting rows.
You should do something like this:
public User registerUser(String usr, String pwd) {
u=em.find(User.class,usr);
if(u!=null)
{
return u;
}
u = new User(usr, pwd);
em.persist(u);
return u;
}
This isn't really the way to go. You are trying to insert a row in a table but have no associated attached entity. If you're using the JPA entity manager - then create a new instance - set the properties & persist the entity.
E.g.
User u = new User();
u.setXXX(xx);
em.persist(u);
// em.flush() <<-- Not required, useful for seeing what is happening
// etc..
If you enable SQL loggging in Hibernate & flush the entity then you'll see what is sent to the database.
E.g. in persistence.xml:
<property name="hibernate.format_sql" value="true" />