JPA Entity not updating correctly - java

I'm using JPA2 with Eclipselink implementation on Glassfish 3.
I have a simple login/logout application.
These are the entity beans an their mappings:
#Entity
#Table(name = "user_account")
public class UserAccount implements Serializable{
#Id
private Integer id;
private String email;
private String password;
private Integer role;
#OneToOne(fetch=FetchType.EAGER)
#PrimaryKeyJoinColumn
private UserDetail userDetail;
// get+set
}
#Entity
#Table(name = "user_detail")
public class UserDetail implements Serializable{
private static final long serialVersionUID = 1L;
#Id
private Integer id;
#Temporal(TemporalType.DATE)
private Date birth;
#Column(name="origin_city")
private String originCity;
#Column(name="residence_city")
private String residenceCity;
private String description;
#OneToOne(mappedBy="userDetail")
private UserAccount userAccount;
}
In a UserService class I have a simple CRUD implementation. All the methods work just fine but the problem is that sometimes the Entity Object saved in the Persistance Context / JPA Cache doesn't synchronize with the database information.
As a specific example, when I delete some user details (from the user_detail table) the database entry is deleted but when I read the information again, the returned entity still has the Details information. I'm guessing that this info was kept in the JPA cache and brought back without checking the database first.
Is this the correct behaviour? Is there a way to keep the Cache information synchronized with the database?
LE : The user Service contains the read(email, password) method which calls a helper method. This helper method contains the CriteriaAPI query which brings all the users with the desired email and password. The Criteria query is tested and works fine.
public UserAccount read(String email, String password){
List<UserAccount> userList = getUserList(null, email, password, Constants.RESULT_SINGLE);
return (userList.isEmpty() ? null : userList.get(0));
}
private List<UserAccount> getUserList(Integer id, String email, String password, String resultType){
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<UserAccount> shell = builder.createQuery(UserAccount.class);
Root<UserAccount> entity = shell.from(UserAccount.class);
shell.select(entity);
shell.distinct(true);
List<Predicate> predicateList = new ArrayList<Predicate>();
if (id != null){
ParameterExpression<Integer> param = builder.parameter(Integer.class, "id");
predicateList.add(builder.equal(entity.get("id"), param));
}
if (email != null){
ParameterExpression<String> param = builder.parameter(String.class, "email");
predicateList.add(builder.equal(entity.get("email"), param));
}
if (password != null){
ParameterExpression<String> param = builder.parameter(String.class, "password");
predicateList.add(builder.equal(entity.get("password"), param));
}
if (predicateList.size() == 1){
shell.where(predicateList.get(0));
} else {
Predicate[] p = new Predicate[predicateList.size()];
p = predicateList.toArray(p);
shell.where(builder.and(p));
}
TypedQuery<UserAccount> selectQuery = em.createQuery(shell);
if (id != null) selectQuery.setParameter("id", id);
if (email != null) selectQuery.setParameter("email", email);
if (password != null) selectQuery.setParameter("password", password);
return selectQuery.getResultList();
}
This is the delete method from the service:
public boolean deleteDetail(Integer userId) {
boolean ok = true;
try {
UserDetail userDetails = em.find(UserDetail.class, userId);
System.out.println("Trying to delete the detail. The address of the object is: " + userDetails);
em.remove(userDetails);
System.out.println("Object deleted. The address is: " + userDetails);
} catch (Exception e) {
e.printStackTrace();
ok = false;
}
return ok;
}

Reading the comments I see that you are using application-managed transactions. As stated in the Java EE Tutorial,
Application-managed entity managers don’t automatically propagate the
JTA transaction context. Such applications need to manually gain
access to the JTA transaction manager and add transaction demarcation
information when performing entity operations. The
javax.transaction.UserTransaction interface defines methods to begin,
commit, and roll back transactions. Inject an instance of
UserTransaction by creating an instance variable annotated with
#Resource...
From the code you have posted, you are not demarcating the transaction that removes the UserDetail record. Moreover, you have not shown the code that you are using for getting the EntityManager reference. You should stick to the same tutorial and use
em = emf.createEntityManager();
for getting an updated instance of the Entity Manager.
Otherwise, switch to Container-Managed transactions that make life much simpler in most of the situations.

Related

Fetching multiple table data using spring and hibernate not working as excepted in UI-Cannot call sendError() after the response has been committed

I have three tables they are Forum,ForumAnswer and ForumAnswerReplay. this table contains #OneToMany relationship. I have Forum table primary key. Based on that primary key I need to get ForumAnswer and ForumAnswerReplay data.
But response is not reaching ui side.
Stack Trace
ResponseStatusExceptionResolver:133 - Resolving exception from handler
[public java.util.List
com.tta.abcd.controller.ForumController.getReplayToAnswer(javax.servlet.http.HttpSession)]:
org.springframework.http.converter.HttpMessageNotWritableException:
Could not write content: Infinite recursion (StackOverflowError)
Forum.java
#Entity
#Table(name="Forum")
public class Forum {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="forumId")
private Long forumId;
#Column(name="question")
private String question;
#Column(columnDefinition="varchar(1000)",name="discription")
private String discription;
#Column(name="postedDate")
private Date postedDate;
#Fetch(value = FetchMode.SELECT)
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "forumId")
#JsonIgnore
private List<ForumAnswer> forumList;
}
ForumAnswer.java
#Entity
#Table(name="ForumAnswer")
public class ForumAnswer {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="forumAnswerId")
private Long forumAnswerId;
#ManyToOne
#JoinColumn(name = "forumId",insertable=true, updatable=true,nullable=true)
private Forum forum;
#Column(name="answer")
private String answer;
#Column(name="answerDate")
private Date answerDate;
#Fetch(value = FetchMode.SELECT)
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "forumAnswerId")
#JsonManagedReference
private List<ForumAnswerReplay> forumAnswerReplayList;
}
ForumAnswerReplay.java
#Entity
#Table(name="ForumAnswerReplay")
public class ForumAnswerReplay {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="replayId")
private Long replayId;
#Column(name="replayToAnswer")
private String replayToAnswer;
#Column(name="replayToAnswerDate")
private Date replayToAnswerDate;
#ManyToOne
#JoinColumn(name="forumAnswerId",insertable=true,updatable=true,nullable=true)
#JsonBackReference
private ForumAnswer forumAnswer;
}
Controller Code:
#RequestMapping(value = "/getForumOnIdAnswer", method = RequestMethod.POST)
#ResponseBody
public List<ForumAnswer> getForumAnswerOnId(#RequestBody Long id, HttpSession session,
HttpServletResponse httpServletResponse, HttpServletRequest httpServletRequest) {
List<ForumAnswer> forumAnswer = forumService.getForumAnswerOnId(id);
if (forumAnswer != null) {
return forumAnswer;
}
return forumAnswer;
}
DAO:
public List<ForumAnswer> getForumAnswerOnId(Long id) {
Long forumId = id;
List<ForumAnswer> ForumAnswerTemp = new ArrayList<ForumAnswer>();
List<ForumAnswerReplay> ForumAnswerReplayTemp = new ArrayList<ForumAnswerReplay>();
Long forumAnswerId = null;
ForumAnswer forumTemp = new ForumAnswer();
ForumAnswer forumEntity = new ForumAnswer();
String getForumAnswer = "from ForumAnswer WHERE forumId =:forumId order by answerDate Desc";
Query query = sessionFactory.getCurrentSession().createQuery(getForumAnswer);
query.setParameter("forumId", forumId);
List<ForumAnswer> forumList = query.list();
int count =forumList.size();
if ( count> 0) {
for (int i=1 ; i < count ;i++) {
forumEntity =forumList.get(i);
forumAnswerId = forumList.get(i).getForumAnswerId();
List<ForumAnswerReplay> repltList = getreplayList(forumAnswerId);
if (repltList.size() > 0) {
ForumAnswerReplayTemp.addAll(repltList);
forumEntity.setForumAnswerReplayList(ForumAnswerReplayTemp);
ForumAnswerTemp.add(forumEntity);
} else {
ForumAnswerTemp.add(forumEntity);
}
}
}
return ForumAnswerTemp;
}
public List<ForumAnswerReplay> getreplayList(Long forumAnswerId) {
String getForumAnswer = "from ForumAnswerReplay WHERE forumAnswerId =:forumAnswerId";
Query query = sessionFactory.getCurrentSession().createQuery(getForumAnswer);
query.setParameter("forumAnswerId", forumAnswerId);
List<ForumAnswerReplay> replays = query.list();
return replays;
}
May be you need to use #Transactional annotation on your impl class.
One of the key points about #Transactional is that there are two separate concepts to consider, each with it's own scope and life cycle:
the persistence context
the database transaction
The transactional annotation itself defines the scope of a single database transaction. The database transaction happens inside the scope of apersistence context.
The persistence context is in JPA the EntityManager, implemented internally using an Hibernate Session (when using Hibernate as the persistence provider).
The persistence context is just a synchronizer object that tracks the state of a limited set of Java objects and makes sure that changes on those objects are eventually persisted back into the database.
This is a very different notion than the one of a database transaction. One Entity Manager can be used across several database transactions, and it actually often is.
https://dzone.com/articles/how-does-spring-transactional
Make return type String in your method deceleration and Convert your list to map and create Model object and set your map in model. then return to your desire jsp page.
#ResponseBody
#RequestMapping(value = "/getForumOnIdAnswer", method = RequestMethod.POST)
public String yourList(#RequestBody Long id, Model model){
List<ForumAnswer> forumAnswer = forumService.getForumAnswerOnId(id);
if (forumAnswer != null) {
// convert your list to Map then set in model.
model.addAttribute("forumAnswerMap",listToMap);
}
return "yourJspPage"
`
May be it will help you..

Retrieve data from many-to-many relationship - Hibernate

I have a 'User' class:
public class User implements Serializable {
#Id
#GeneratedValue
int id;
String nome;
#Column(unique = true)
String email;
#ManyToMany
#JoinTable (name = "user_roles", joinColumns=
{ #JoinColumn (name = "user_id")}, inverseJoinColumns=
{ #JoinColumn (name = "role_id")})
private List<Role> roles;
I have a 'Role' class:
public class Role implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
int id;
#Column(unique = true)
String role;
Relationship :An User has roles, and roles has users. I have a many-to-many relationship here. The relation is unidirectional, and my dominant entity is User.
In my database has a table named "user_roles", created automatically. I have a User registered with two roles.
When I retrieve the User from my database using a "UserDao" class, and I try to access the roles of the user, I get NullPointerException. I got all others informations (name, email, etc), but the roles I can not access.
My 'UserDao' class:
public class UserDaoImpl implements UserDao{
private static final transient Logger log = LoggerFactory.getLogger(UserDaoImpl.class);
private final String HIBERNATE_CFG = "data.cfg.xml";
#Override
public int insertUser (User user){
int code = 0;
Session session = new HibernateUtil(HIBERNATE_CFG).getSessionFactory().getCurrentSession();
try {
session.beginTransaction();
session.save(user);
session.getTransaction().commit();
code = 1;
}
catch (Exception e) {
log.error(e.getMessage());
session.getTransaction().rollback();
code = 0;
}
return code;
}
#Override
public List<User> getAll() {
Session session = new HibernateUtil(HIBERNATE_CFG).getSessionFactory().getCurrentSession();
List<User> users = null;
try
{
session.beginTransaction();
String queryString = "SELECT * FROM USER;";
SQLQuery consulta = session.createSQLQuery(queryString);
consulta.setResultTransformer(Transformers.aliasToBean(User.class));
users = (List<User>) consulta.list();
session.getTransaction().commit();
}
catch(ConstraintViolationException e)
{
log.error(e.getMessage());
session.getTransaction().rollback();
}
return users;
}
I tried using iterator:
List roles = user.getRegras();
for (Iterator it = roles.iterator(); it.hasNext();){
Role r = (Role) it.next();
out.println("Role:" + r.getRole());
}
I tried to instantiate the variable "roles" in the 'User' class like below. But every time the variable returns size 0:
#ManyToMany
#JoinTable (name = "user_roles", joinColumns=
{ #JoinColumn (name = "user_id")}, inverseJoinColumns=
{ #JoinColumn (name = "role_id")})
private List<Role> regras = new ArrayList<Role>();
I tried getting the roles from Object User, but I receive a NullPointerException when the method size() is called.
List<Role> roles = (List<Role>)user.getRoles();
out.println("size roles: " + roles.size());
I'm trying to acess my table 'user_roles' to retrieve the data from database, but I can't dothis. Can anyone help me?
Thanks in Advance
You're using a SQL query to load the users, and mapping the result to the entity. So what you get is detached entities, containing only what the SQL query returned.
Learn to use JPQL/HQL, and to load entities, which will then be managed:
String queryString = "SELECT u FROM User u";
Query consulta = session.createQuery(queryString);
users = (List<User>) consulta.list();

JPA recursive one-to-one lazy-loaded reference not updated

Using JPA 2.1 and Hibernate 4.3.6.Final, I have the following simple entity:
#Entity
#Table(name = "CONTACT")
public class Contact {
#Id
#Column(name = "ID")
private String id = UUID.randomUUID().toString();
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PARTNER_ID")
private Contact partner;
Contact() {
}
public void assignPartner(final Contact other) {
this.partner = Objects.requireNonNull(other);
other.partner = this;
}
public void unassignPartner() {
if (partner != null) {
partner.partner = null;
}
partner = null;
}
}
Notice the lazy-loaded one-to-one recursive association to a partner Contact. Also notice how assignPartner() and unassignPartner() manage the bi-directional relationship.
And the following methods:
private static void assignPartner(final EntityManager entityManager) {
entityManager.getTransaction().begin();
final Contact contact1 = entityManager.find(Contact.class, CONTACT1_ID);
final Contact contact2 = entityManager.find(Contact.class, CONTACT2_ID);
contact1.assignPartner(contact2);
entityManager.getTransaction().commit();
}
private static void unassignPartner(final EntityManager entityManager) {
entityManager.getTransaction().begin();
final Contact contact1 = entityManager.find(Contact.class, CONTACT1_ID);
contact1.unassignPartner();
entityManager.getTransaction().commit();
}
Assuming existing rows for CONTACT1_ID and CONTACT2_ID, after running assignPartner() then unassignPartner(), database state shows that contact1 has a null partner_id and contact2 still has a non-null partner_id.
However, if I change the Contact.partner fetch type to EAGER, after running assignPartner() then unassignPartner(), database state shows that both contact1 and contact2 have null partner_id.
Why is that? Why are changes to the partner entity not flushed to the database?
EDIT 1
Changes to the partner reference through direct field access, e.g. partner.firstName = "DUMPED", are not propagated either.
Changes to the partner reference through method access, e.g. partner.setFirstName("DUMPED"), are propagated.
Neither partner.partner = null or partner.setPartner(null) are propagated.
EDIT 2
As suggested by Rat2000, moving the unassignment logic outside the Contact.unassignPartner() method and inside the unassignPartner(EntityManager) method seems to work properly. So it's really something to do with how Hibernate deals with the contact1.partner proxy, and in particular the contact1.partner.partner proxy.
final Contact contact1 = entityManager.find(Contact.class, CONTACT1_ID);
contact1.getPartner().unassignPartner();
contact1.unassignPartner();
Try this:
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.UPDATE)
#JoinColumn(name = "PARTNER_ID")
private Contact partner;

JPA: How to get entity based on field value other than ID?

In JPA (Hibernate), when we automatically generate the ID field, it is assumed that the user has no knowledge about this key. So, when obtaining the entity, user would query based on some field other than ID. How do we obtain the entity in that case (since em.find() cannot be used).
I understand we can use a query and filter the results later. But, is there a more direct way (because this is a very common problem as I understand).
It is not a "problem" as you stated it.
Hibernate has the built-in find(), but you have to build your own query in order to get a particular object. I recommend using Hibernate's Criteria :
Criteria criteria = session.createCriteria(YourClass.class);
YourObject yourObject = criteria.add(Restrictions.eq("yourField", yourFieldValue))
.uniqueResult();
This will create a criteria on your current class, adding the restriction that the column "yourField" is equal to the value yourFieldValue. uniqueResult() tells it to bring a unique result. If more objects match, you should retrive a list.
List<YourObject> list = criteria.add(Restrictions.eq("yourField", yourFieldValue)).list();
If you have any further questions, please feel free to ask. Hope this helps.
if you have repository for entity Foo and need to select all entries with exact string value boo (also works for other primitive types or entity types). Put this into your repository interface:
List<Foo> findByBoo(String boo);
if you need to order results:
List<Foo> findByBooOrderById(String boo);
See more at reference.
Basically, you should add a specific unique field. I usually use xxxUri fields.
class User {
#Id
// automatically generated
private Long id;
// globally unique id
#Column(name = "SCN", nullable = false, unique = true)
private String scn;
}
And you business method will do like this.
public User findUserByScn(#NotNull final String scn) {
CriteriaBuilder builder = manager.getCriteriaBuilder();
CriteriaQuery<User> criteria = builder.createQuery(User.class);
Root<User> from = criteria.from(User.class);
criteria.select(from);
criteria.where(builder.equal(from.get(User_.scn), scn));
TypedQuery<User> typed = manager.createQuery(criteria);
try {
return typed.getSingleResult();
} catch (final NoResultException nre) {
return null;
}
}
Best practice is using #NaturalId annotation. It can be used as the business key for some cases it is too complicated, so some fields are using as the identifier in the real world.
For example, I have user class with user id as primary key, and email is also unique field. So we can use email as our natural id
#Entity
#Table(name="user")
public class User {
#Id
#Column(name="id")
private int id;
#NaturalId
#Column(name="email")
private String email;
#Column(name="name")
private String name;
}
To get our record, just simply use 'session.byNaturalId()'
Session session = sessionFactory.getCurrentSession();
User user = session.byNaturalId(User.class)
.using("email","huchenhai#qq.com")
.load()
This solution is from Beginning Hibernate book:
Query<User> query = session.createQuery("from User u where u.scn=:scn", User.class);
query.setParameter("scn", scn);
User user = query.uniqueResult();
I solved a similar problem, where I wanted to find a book by its isbnCode not by your id(primary key).
#Entity
public class Book implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private String isbnCode;
...
In the repository the method was created like #kamalveer singh mentioned. Note that the method name is findBy+fieldName (in my case: findByisbnCode):
#Repository
public interface BookRepository extends JpaRepository<Book, Integer> {
Book findByisbnCode(String isbnCode);
}
Then, implemented the method in the service:
#Service
public class BookService {
#Autowired
private BookRepository repo;
public Book findByIsbnCode(String isbnCode) {
Book obj = repo.findByisbnCode(isbnCode);
return obj;
}
}
Write a custom method like this:
public Object findByYourField(Class entityClass, String yourFieldValue)
{
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Object> criteriaQuery = criteriaBuilder.createQuery(entityClass);
Root<Object> root = criteriaQuery.from(entityClass);
criteriaQuery.select(root);
ParameterExpression<String> params = criteriaBuilder.parameter(String.class);
criteriaQuery.where(criteriaBuilder.equal(root.get("yourField"), params));
TypedQuery<Object> query = entityManager.createQuery(criteriaQuery);
query.setParameter(params, yourFieldValue);
List<Object> queryResult = query.getResultList();
Object returnObject = null;
if (CollectionUtils.isNotEmpty(queryResult)) {
returnObject = queryResult.get(0);
}
return returnObject;
}
Edit: Just realized that #Chinmoy was getting at basically the same thing, but I think I may have done a better job ELI5 :)
If you're using a flavor of Spring Data to help persist / fetch things from whatever kind of Repository you've defined, you can probably have your JPA provider do this for you via some clever tricks with method names in your Repository interface class. Allow me to explain.
(As a disclaimer, I just a few moments ago did/still am figuring this out for myself.)
For example, if I am storing Tokens in my database, I might have an entity class that looks like this:
#Data // << Project Lombok convenience annotation
#Entity
public class Token {
#Id
#Column(name = "TOKEN_ID")
private String tokenId;
#Column(name = "TOKEN")
private String token;
#Column(name = "EXPIRATION")
private String expiration;
#Column(name = "SCOPE")
private String scope;
}
And I probably have a CrudRepository<K,V> interface defined like this, to give me simple CRUD operations on that Repository for free.
#Repository
// CrudRepository<{Entity Type}, {Entity Primary Key Type}>
public interface TokenRepository extends CrudRepository<Token, String> { }
And when I'm looking up one of these tokens, my purpose might be checking the expiration or scope, for example. In either of those cases, I probably don't have the tokenId handy, but rather just the value of a token field itself that I want to look up.
To do that, you can add an additional method to your TokenRepository interface in a clever way to tell your JPA provider that the value you're passing in to the method is not the tokenId, but the value of another field within the Entity class, and it should take that into account when it is generating the actual SQL that it will run against your database.
#Repository
// CrudRepository<{Entity Type}, {Entity Primary Key Type}>
public interface TokenRepository extends CrudRepository<Token, String> {
List<Token> findByToken(String token);
}
I read about this on the Spring Data R2DBC docs page, and it seems to be working so far within a SpringBoot 2.x app storing in an embedded H2 database.
No, you don't need to make criteria query it would be boilerplate code you just do simple thing if you working in Spring-boot:
in your repo declare a method name with findBy[exact field name].
Example-
if your model or document consist a string field myField and you want to find by it then your method name will be:
findBymyField(String myField);
All the answers require you to write some sort of SQL/HQL/whatever. Why? You don't have to - just use CriteriaBuilder:
Person.java:
#Entity
class Person {
#Id #GeneratedValue
private int id;
#Column(name = "name")
private String name;
#Column(name = "age")
private int age;
...
}
Dao.java:
public class Dao {
public static Person getPersonByName(String name) {
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
session.beginTransaction();
CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<Person> cr = cb.createQuery(Person.class);
Root<Person> root = cr.from(Person.class);
cr.select(root).where(cb.equal(root.get("name"), name)); //here you pass a class field, not a table column (in this example they are called the same)
Query query = session.createQuery(cr);
query.setMaxResults(1);
List<Person> resultList = query.getResultList();
Person result = resultList.get(0);
return result;
}
}
example of use:
public static void main(String[] args) {
Person person = Dao.getPersonByName("John");
System.out.println(person.getAge()); //John's age
}
Have a look at:
JPA query language: The Java Persistence Query Language
JPA Criteria API: Using the Criteria API to Create Queries
I've written a library that helps do precisely this. It allows search by object simply by initializing only the fields you want to filter by: https://github.com/kg6zvp/GenericEntityEJB
Refer - Spring docs for query methods
We can add methods in Spring Jpa by passing diff params in methods like:
List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);
// Enabling static ORDER BY for a query
List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
In my Spring Boot app I resolved a similar type of issue like this:
#Autowired
private EntityManager entityManager;
public User findByEmail(String email) {
User user = null;
Query query = entityManager.createQuery("SELECT u FROM User u WHERE u.email=:email");
query.setParameter("email", email);
try {
user = (User) query.getSingleResult();
} catch (Exception e) {
// Handle exception
}
return user;
}
This is very basic query :
Entity : Student
#Entity
#Data
#NoArgsConstructor
public class Student{
#Id
#GeneratedValue(generator = "uuid2", strategy = GenerationType.IDENTITY)
#GenericGenerator(name = "uuid2", strategy = "uuid2")
private String id;
#Column(nullable = false)
#Version
#JsonIgnore
private Integer version;
private String studentId;
private String studentName;
private OffsetDateTime enrollDate;
}
Repository Interface : StudentRepository
#Repository
public interface StudentRepository extends JpaRepository<Student, String> {
List<Student> findByStudentName(String studentName);
List<Student> findByStudentNameOrderByEnrollDateDesc(String studentName);
#Transactional
#Modifying
void deleteByStudentName(String studentName);
}
Note:
findByColumnName : give results by criteria
List findByStudentName(String studentName)
Internally convert into query : select * from Student where name='studentName'
#Transactional
#Modifying
Is useful when you want to remove persisted data from database.
Using CrudRepository and JPA query works for me:
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
public interface TokenCrudRepository extends CrudRepository<Token, Integer> {
/**
* Finds a token by using the user as a search criteria.
* #param user
* #return A token element matching with the given user.
*/
#Query("SELECT t FROM Token t WHERE LOWER(t.user) = LOWER(:user)")
public Token find(#Param("user") String user);
}
and you invoke the find custom method like this:
public void destroyCurrentToken(String user){
AbstractApplicationContext context = getContext();
repository = context.getBean(TokenCrudRepository.class);
Token token = ((TokenCrudRepository) repository).find(user);
int idToken = token.getId();
repository.delete(idToken);
context.close();
}

Using Hibernate Criteria to retrieve data from multiple tables without entity mapping setting

I have a problem in my project. I am trying to create a search function to search users from the user table, but at the meantime, I also want to retrieve the corresponding "url" of user's avatar from another Avatar table. I do want to create a hard mapping between these two tables. How can I do it flexibly using Hibernate Criteria? Both tables are using primary key of "loginID".
I have two classes:
public class User{
private String loginID;
private String screenname;
......
}
public class Avatar{
private Integer id;
private String loginID;
private String url;
.......
}
What I have written:
public List<Users> searchLogin(String keywords, int startFrom) {
List<Users> userList = new ArrayList<Users>();
try {
Session session = HibernateUtil.beginTransaction();
Criteria criteria = session.createCriteria(Users.class,"users");
criteria.add(Restrictions.ilike("loginID", keywords, MatchMode.ANYWHERE));
userList = criteria.list();
if (session.isOpen()) {
session.close();
}
return userList;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
Thanks guys!!
Late, but it could be useful for others who actually google it and ends up here.
No need to map or use HQL.
Here's how:
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Tuple> criteria = builder.createTupleQuery();
Root<EntityA> entityARoot= criteria.from(EntityA.class);
Root<EntityB> entityBRoot = criteria.from(EntityB.class);
//Predicates
List<Predicate> predicates = new ArrayList<>();
//Add the predicates you need
//And predicates
List<Predicate> andPredicates = new ArrayList<>();
andPredicates.add(builder.equal(entityARoot.get("id"), entityBRoot.get("id")));
andPredicates.add(builder.and(predicates.toArray(new Predicate[0])));
criteria.multiselect(entityARoot, entityBRoot);
criteria.where(andPredicates.toArray(new Predicate[0]));
TypedQuery<Tuple> query = em.createQuery(criteria);
List<Tuple> result = query.getResultList();
Use HQL
from User u,Avatar a where where u.loginID = a.loginID and u.loginID = :loginID
This will return a list of [User,Avatar] arrays.
Both tables are using primary key of "loginID".
Is this correct? I see the Avatar class having a field named 'id', isn't this the id/primary key?
I also noticed that you refer from Avatar to User using the loginId field. A correct way of linking entities is by their class. Using annotations it should look like this:
#Entity
public class User
{
#Id
private String loginId;
private String screenName;
#OneToOne(mappedBy = "user")
private Avatar avatar;
}
#Entity
public class Avatar
{
#Id
private Integer id;
#OneToOne
private User user;
private String url;
}
If you then want to fetch a User and the URL of the Avatar belonging to the User the best thing you can do is fetch a User and it's Avatar using a join and then access the Avatar's URL and thus not having to cast from Object to User and Avatar preserving type safety.
User user = fetchUserJoinAvatar("123");
String url = user.getAvatar().getUrl();
public User fetchUserJoinAvatar(String loginId)
{
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> user = query.from(User.class);
user.fetch("avatar");
query.select(user).where(cb.equal(user.get("loginId"), loginId));
return em.createQuery(query).getSingleResult();
}

Categories

Resources