I'm still struggling with changing my Spring Application to use Hibernate with JPA to do database activities. Well apparently from a previous post I need an persistence.xml file. However do I need to make changes to my current DAO class?
public class JdbcProductDao extends Dao implements ProductDao {
/** Logger for this class and subclasses */
protected final Log logger = LogFactory.getLog(getClass());
public List<Product> getProductList() {
logger.info("Getting products!");
List<Product> products = getSimpleJdbcTemplate().query(
"select id, description, price from products",
new ProductMapper());
return products;
}
public void saveProduct(Product prod) {
logger.info("Saving product: " + prod.getDescription());
int count = getSimpleJdbcTemplate().update(
"update products set description = :description, price = :price where id = :id",
new MapSqlParameterSource().addValue("description", prod.getDescription())
.addValue("price", prod.getPrice())
.addValue("id", prod.getId()));
logger.info("Rows affected: " + count);
}
private static class ProductMapper implements ParameterizedRowMapper<Product> {
public Product mapRow(ResultSet rs, int rowNum) throws SQLException {
Product prod = new Product();
prod.setId(rs.getInt("id"));
prod.setDescription(rs.getString("description"));
prod.setPrice(new Double(rs.getDouble("price")));
return prod;
}
}
}
Also my Product.Java is below
public class Product implements Serializable {
private int id;
private String description;
private Double price;
public void setId(int i) {
id = i;
}
public int getId() {
return id;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("Description: " + description + ";");
buffer.append("Price: " + price);
return buffer.toString();
}
}
I guess my question would be,
How would my current classes change after using Hibernate + JPA with an Entity Manager
Did you check the section 12.6. JPA of the Chapter 12. Object Relational Mapping (ORM) data access in the official documentation? Everything you need to know is discussed there.
If this is an option, you should extend the JpaDaoSupport base class, it provides convenient methods like get/setEntityManagerFactory and getJpaTemplate() to be used by sublasses. If not, then get a EntityManagerFactory injected and use it to create a JpaTemplate. See the section 12.6.2. JpaTemplate and JpaDaoSupport for more details on this.
Also have a look at Getting Started With JPA in Spring 2.0 for a more complete sample (the blog post is a bit old but is not really outdated) that will show you how to rewrite the methods of your DAO.
Moreover, I suggest you to read this article: Generic DAO with Hibernate and Spring AOP, before deciding your design.
Related
I am new to spring.
I just tried successfully using an entity class without #Id in Spring Data JDBC
Custom query was added in my repository for retrieving data from 2 mysql tables and returning an entity having the joined table data.
If I plan to use only custom queries, am I missing anything here?
Here's my entity class without #Id or #Entity:
public class Item
{
private long id;
private String code;
private String itemName;
private String groupName;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
public String getGroupName() {
return groupName;
}
public void setGroupName(String groupName) {
this.groupName = groupName;
}
}
Repository layer:
#Repository
public interface ItemRepository extends PagingAndSortingRepository<Item, Long>
{
#Query("SELECT a.id, a.code, a.name AS item_name,
b.name as group_name from item a, item_group b
WHERE a.group_id = b.id AND a.id=:id")
Item findItemById(#Param("id") Long id);
}
Service layer:
#Service
public class ItemServiceImpl implements ItemService
{
private final ItemRepository itemRepository;
public ItemServiceImpl(ItemRepository itemRepository)
{
this.itemRepository = itemRepository;
}
#Override
#Transactional(readOnly=true)
public Item findItemById(Long id)
{
return itemRepository.findItemById(id);
}
}
My updated main Configuration class in response to answer of Jens:
#SpringBootApplication
#EnableJdbcRepositories
public class SpringDataJdbcApplication extends AbstractJdbcConfiguration
{
public static void main(String[] args)
{
SpringApplication.run(SpringDataJdbcApplication.class, args);
}
#Bean
#ConfigurationProperties(prefix="spring.datasource")
public DataSource dataSource()
{
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
return dataSourceBuilder.build();
}
#Bean
NamedParameterJdbcOperations namedParameterJdbcOperations(DataSource dataSource)
{
return new NamedParameterJdbcTemplate(dataSource);
}
#Bean
PlatformTransactionManager transactionManager()
{
return new DataSourceTransactionManager(dataSource());
}
}
If you don't get any exceptions you should be fine. There shouldn't be anything in Spring Data JDBC that silently breaks when the id is not specified.
The problem is though: I don't consider it a feature that this works, but just accidental behaviour. This means it might break with any version, although replacing these methods with custom implementations based on a NamedParameterJdbcTemplate shouldn't be to hard, so the risk is limited.
The question though is: Why don't you add the #Id annotation, after all your entity does have an id. And the whole idea of a repository conceptually requires an id.
If it's working and you really don't want to use the annotations, you can do it. But I think that it's unnecessary complication. You can expect errors that would not be there if you had used the annotations and code will be harder to debug. If you are new in Spring I recommend to use annotations. But after all it depend on you how will you design your applications. For sure advantage of approach without annotations is higher control about database.
I know English badly, but i'm trying to describe my problem.
I'm new in Spring. And I have some problems with adding data to my database.I have to table Pc and Pc characteristics. They are related by Id. It's easy to add data in non realted table, but how can I add data in related table? What shoud I write in my Controller? There are some classes below.
Pc class:
#Entity
#Table(name = "pc")
public class Pc {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
private int price;
public Pc(){}
public Pc(String name, int price) {
this.name = name;
this.price = price;
}
#OneToMany
#JoinColumn(name = "pc_id")
private List<PcChars> chars = new ArrayList<>();
public List<PcChars> getChars() {
return chars;
}
public void setChars(List<PcChars> chars) {
this.chars = chars;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
PcChars class:
#Entity
#Table(name = "pcChars")
public class PcChars {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
private String value;
public PcChars(){}
public PcChars(String name, String value) {
this.name = name;
this.value = value;
}
#ManyToOne
private Pc pc;
public Pc getPc() {
return pc;
}
public void setPc(Pc pc) {
this.pc = pc;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
PcCharactsController:
#Controller
public class PcCharactsController {
final private PcRepo pcRepo;
final private PcCharRepo pcCharRepo;
public PcCharactsController(PcRepo pcRepo, PcCharRepo pcCharRepo) {
this.pcRepo = pcRepo;
this.pcCharRepo = pcCharRepo;
}
//Pc characteristics list
#GetMapping("pc/{id}/")
public String pcCharList(#PathVariable int id, Model model) throws Exception{
Pc pc = pcRepo.findById(id).orElseThrow(() -> new Exception("PostId " +
id + " not found"));
List<PcChars> pcChars = pc.getChars();
model.addAttribute("model", pc.getName());
model.addAttribute("pcChars", pcChars);
return "charList";
}
//add characteristic
#PostMapping("pc/{id}/")
public String addCharact(){
return "charList";
}
Characteristics.ftl:
<html>
<head>
<title>Ho</title>
</head>
<body>
<div>
<form method="post" action="/pc/${id}/">
<input type="text" name="name">
<input type="text" value="value">
<input type="hidden" name="pc_id" value="${id}">
<button type="submit">Add</button>
</form>
</div>
</body>
</html>
Since you are not using any modelAttribute to bind the input values straight to a POJO you can use simple HttpServletRequest to get the input attributes, use them to create the object you want to store and store it using Hibernate
#PostMapping("pc/{id}/")
public String addCharact(HttpServletRequest req){
String name = req.getParameter("name");
String value = req.getParameter("value");
String id = req.getParameter("id");
PcChars pcchars = new PcChars(name,value,id); // create the corresponding constructor
SessionFactory sessionFactory;
Session session = sessionFactory.openSession();
Transaction tx = null;
try{
tx = session.getTransaction();
tx.begin();
session.save(pcchars);
tx.commit();
}
catch (HibernateException e) {
if (tx!=null) tx.rollback();
e.printStackTrace();
} finally {
session.close();
}
return "charList";
}
The part of Spring you're using is called Spring data, a library that allows you to use JPA in your Spring application. JPA is a specification for frameworks called ORM (Object-relationnal mapping).
To keep it simple, in your code, you do not use the relational approach anymore, but an object approach. Annotations you put on your classes' fields are used to define mappings between them and your database tables and fields.
So, you don't have to insert both entities separately anymore. You need to create a Pc instance, then to create a PcChars one, and finally to add the chars into the pc's chars list, like this :
Pc myPc = new Pc();
PcChars myChars = new PcChars();
myPc.getChars().add(myChars);
And when you'll use your repository to save the modifications with this :
pcRepo.save(myPc);
The JPA implementation will automatically do the work for you :
Inserting the row corresponding to your PC instance in the PC table
Inserting the row corresponding to your PC chars in the the PC_CHARS table
Settings the PC_CHARS.PC_ID with the ID of the freshly inserted PC instance's id in order to create the reference between them.
Not sure, but I think the ORM also do this when you add your chars to the pc instance :
myChars.setPc(myPc);
in order to make the bound between both instances reciprocal.
Note that I used arbitrary field names according to your schema.
I strongly suggest you to give responsibility of relationship to child side when using #OneToMany relation.
Modify your parent class as below:
#OneToMany(cascade = CascadeType.ALL, mappedBy="pc")
#BatchSize(size = 10)
private List<PcChars> chars = new ArrayList<>();
public void addPcChar(PcChar pcChar) {
this.chars.add(pcChar);
pcChar.setPc(this);
}
On the child class:
#ManyToOne
#JoinColumn(name = "pc_id")
private Pc pc;
Now you can persist your parent with child as below :
Pc pc = new Pc();
PcChar pcChar = new PcChar();
pc.addPcChar(pcChar);
If you use spring boot data repository, it saves it correctly as below
// assume your repository like below
public interface PcRepository extends CrudRepository<Pc, Integer> {}
// in your service or whatever in some place
pcRepository.save(pc);
With saving hibernate entity manager:
EntityManagerFactory emfactory =
Persistence.createEntityManagerFactory("Hibernate");
EntityManager entitymanager = emfactory.createEntityManager();
entitymanager.getTransaction().begin();
entitymanager.persist(pc);
entitymanager.getTransaction().commit();
entitymanager.close();
emfactory.close();
For detailed information about hibernate relationship take a look at my post : https://medium.com/#mstrYoda/hibernate-deep-dive-relations-lazy-loading-n-1-problem-common-mistakes-aff1fa390446
i have problem with saving data in DB.I'm new in Spring Boot. When i run my program the result of writen data is: packagename#randomcode example:com.abc.patient.Patient#6e3e681e
This is my Entity class - Patient.java
#Entity
public class Patient {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
// getter, setter, constructor, etc
}
This is my CrudRepo PatientRepository.java
public interface PatientRepository extends CrudRepository<Patient,Integer> {
}
This is my Service class PatientService.java
#Service
public class PatientService {
#Autowired
private PatientRepository patientRepository;
public void savePatient (String name) {
Patient patient = new Patient(name);
patientRepository.save(patient);
}
public Optional<Patient> showPatient(int id) {
return patientRepository.findById(id);
}
public List<Patient> showAllPatients() {
List<Patient> patients = new ArrayList<>();
patientRepository.findAll().forEach(patients::add);
return patients;
}
}
I think that problem in in the savePatient method in this line:
Patient patients = new Patient(name);
I checked the "name" parameter and it's in 100% correct String. I'm using Derby DB.
The only problem you have is how you are printing out your Patient class. Define a proper toString() or just debug yourself to see the resulting fields. There is no problem in your JPA implementation.
See this question for the details of default toString
Try:
public void savePatient(Patient patient) {
patientRepository.save(patient);
}
I have just completed an upgrade from Hibernate 3.6 to 4.1.3 Final and at first everything seemed to go fine. However, one of my colleagues recently tested this an in one scenario he gets a NullPointer being thrown from within Hibernate (and this exception was not being thrown before we upgraded for the exact same DB). It is an incredibly strange scenario. We have an entity called BlogPost that looks like the below and it extends some mapped superclasses (that I have also included):
#Entity
#Table(name = "blog_post")
public class BlogPost extends CommunityModelObject implements HasFeedPost {
#Lob
private String title;
#Lob
private String content;
#Enumerated
#Column(nullable = false)
private CBlogPost.Status status = CBlogPost.Status.UNPUBLISHED;
// Reference to the feed post that indicates that this blog post has been published
#OneToOne
#JoinColumn(name = "feed_post_id")
private FeedPost feedPost;
#ManyToOne
#JoinColumn(name = "posted_by_employee_id")
private Employee postedBy;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public CBlogPost.Status getStatus() {
return status;
}
public void setStatus(CBlogPost.Status status) {
this.status = status;
}
#Override
public FeedPost getFeedPost() {
return feedPost;
}
#Override
public void setFeedPost(FeedPost feedPost) {
this.feedPost = feedPost;
}
public Employee getPostedBy() {
return postedBy;
}
public void setPostedBy(Employee postedBy) {
this.postedBy = postedBy;
}
}
#Filter(name = "tenantFilter", condition = "(tenant_id = :tenantId or tenant_id is null)")
#MappedSuperclass
public abstract class CommunityModelObject extends ModelObject {
#IndexedEmbedded(prefix = "tenant", indexNullAs = IndexedEmbedded.DEFAULT_NULL_TOKEN)
#ManyToOne
#JoinColumn(name = "tenant_id")
protected Tenant tenant;
public Tenant getTenant() {
return tenant;
}
public void setTenant(Tenant tenant) {
this.tenant = tenant;
}
/**
* If the Tenant is null then it can be accessed / viewed by the entire "community" / user base
*/
public boolean isCommunityObject() {
return tenant == null;
}
}
#MappedSuperclass
public abstract class ModelObject extends BaseModelObject {
#Id
#GeneratedValue
private Long id;
#Override
public long getId() {
return (id == null ? 0 : id);
}
public void setId(long id) {
this.id = (id == 0 ? null : id);
}
}
#MappedSuperclass
public abstract class BaseModelObject implements java.io.Serializable {
// This annotation ensures that a column is not associated with this member (simply omitting the #Column annotation is not enough since
// that annotation is completely optional)
#Transient
private boolean doNotAutoUpdateDateUpdated = false;
#Version
protected int version;
#Column(name = "date_created")
protected Date dateCreated;
#Column(name = "date_updated")
protected Date dateUpdated;
public abstract long getId();
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
public Date getDateCreated() {
return dateCreated;
}
public Date getDateUpdated() {
return dateUpdated;
}
/**
* This will set the dateUpdated to whatever is passed through and it will cause the auto update (pre-update) to NOT occur
*
* #param dateUpdated
*/
public void setDateUpdated(Date dateUpdated) {
doNotAutoUpdateDateUpdated = true;
this.dateUpdated = dateUpdated;
}
public void touch() {
// By setting date updated to null this triggers an update which results in onUpdate being called and the nett
// result is dateUpdated = new Date()
dateUpdated = null;
}
#PrePersist
protected void onCreate() {
dateCreated = new Date();
}
#PreUpdate
protected void onUpdate() {
if (!doNotAutoUpdateDateUpdated) {
dateUpdated = new Date();
}
}
#Override
public boolean equals(Object obj) {
long id = getId();
if (id == 0) {
return this == obj;
}
//Use Hibernate.getClass() because objects might be proxies
return obj != null &&
obj instanceof BaseModelObject &&
Hibernate.getClass(this) == Hibernate.getClass(obj) &&
getId() == ((BaseModelObject)obj).getId();
}
#Override
public int hashCode() {
Long id = getId();
return id == 0 ? super.hashCode() : id.intValue();
}
#Override
public String toString() {
return getClass().getSimpleName() + "-" + getId();
}
}
The strangest thing is happening when I query BlogPost in some scenarios. If I run the query below, for example, in isolation then it works fine but if I run it in amongst a bunch of other queries then I get the exception below:
select b from BlogPost b
java.lang.NullPointerException
at org.hibernate.event.internal.DefaultFlushEntityEventListener.isUpdateNecessary(DefaultFlushEntityEventListener.java:240)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:163)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:225)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:99)
at org.hibernate.event.internal.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:55)
at org.hibernate.internal.SessionImpl.autoFlushIfRequired(SessionImpl.java:1153)
at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1208)
at org.hibernate.internal.QueryImpl.list(QueryImpl.java:101)
at org.hibernate.ejb.QueryImpl.getResultList(QueryImpl.java:256)
Now the kicker is that if I take all of the fields from all of the mapped superclasses that I listed above and put them directly into BlogPost and make BlogPost just extend nothing and implement java.io.Serializable then everything works perfectly. This leads me to believe that the bug is either related to mapped superclasses or the Hibernate filter that I am applying to CommunityModelObject.
Any ideas as to how to solve this? I am assuming that it is a newly introduced bug in Hibernate but I may be wrong. This is causing major issues for us since we need to upgrade asap in order to upgrade Hibernate Search which we need to do for a critical bug fix.
Also note that the DB we are using is MySQL with the following custom dialect that I wrote when doing this upgrade to handle our BIT columns:
public class MySQL5InnoDBDialectExt extends MySQL5InnoDBDialect {
private static final String BIT_STRING = "bit";
public MySQL5InnoDBDialectExt() {
super();
registerColumnType(Types.BOOLEAN, BIT_STRING);
}
}
Thanks,
Brent
I sorted this issue out, found the problem by fluke. Here is the resolution as I posted it on the Hibernate forum:
I found the issue. It does not seem to be related to interceptors,
rather to either caching or instrumentation. Basically our app
automatically includes all entities within a very specific package in
our caching scheme and the same classes in our instrumentation. We
generally have all of our entities in this package, however this one
which was causing the issue was the only one not included in this
package. The previous version of EhCache / Hibernate that we were
using seemed ok with this, but after upgrading it caused issues.
Anyway, the entity was in the incorrect package, when I refactored it
and moved it into the correct package then everything worked! So it
was not a bug in Hibernate, just an informative exception that made it
difficult to track this issue down (I basically solved it by complete
fluke).
Hope this helps somebody, but in my case it was a problem with a wrong instrumentation.
I have class 'A' and two child classes 'B' and 'C'. 'A' class has a lazy property and it is instrumented to make the lazy accessing works.
But the mistake was that I didn't instrument the child classes 'B' and 'C', therefore any access to the instrumented property from 'B' and 'C' caused the exception.
When I instrumented 'B' and 'C', the problem went away.
I am trying out some examples from Beginning Java EE6 with GlassFish3 .So , i created an entity class that basically looks like this ...
#Entity
#Table(name="Book")
public class Book implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(nullable=false)
private String name;
#Column(nullable=false)
private String isbn;
private String description;
public Book()
{
// Empty constructor to facilitate construction.
System.out.println("The variables have not been initialized...Please initialize them using the Setters or use the provided constructor");
}
public Book(String name, String isbn, String description) {
this.name = name;
this.isbn = isbn;
this.description = description;
}
public String getIsbn() {
return isbn;
}
public void setIsbn(String isbn) {
this.isbn = isbn;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
#Override
public String toString() {
return this.name + " - " + this.isbn;
}
#PrePersist
public void printPrePersist(){
System.out.println("Persisting the book "+this.name);
}
#PostPersist
public void printPostPersist(){
System.out.println("Persisted the book "+this.name);
}
}
and i tried to persist it like this ...
public class MainClass
{
public static void main(String[] args){
Book book = new Book("Effective Java","ISBN - 1234415","A very good book on Java");
Book book2 = new Book("Learning Java EE","ISBN - 1233415","A good book for Java EE beginners");
// These are the necessary classes
EntityManagerFactory emf = Persistence.createEntityManagerFactory("PersistenceAppPU");
EntityManager em = emf.createEntityManager();
// Persist the book here
EntityTransaction etx = em.getTransaction();
etx.begin();
em.persist(book);
em.persist(book2);
etx.commit();
em.close();
emf.close();
System.out.println("The two books have been persisted");
}
}
It persists , but when i run , i see an output like ...
The variables have not been initialized...Please initialize them using the Setters or use the provided constructor
Persisting the book Effective Java
The variables have not been initialized...Please initialize them using the Setters or use the provided constructor
Persisting the book Learning Java EE
Persisted the book Learning Java EE
Persisted the book Effective Java
The variables have not been initialized...Please initialize them using the Setters or use the provided constructor
The variables have not been initialized...Please initialize them using the Setters or use the provided constructor
The variables have not been initialized...Please initialize them using the Setters or use the provided constructor
The variables have not been initialized...Please initialize them using the Setters or use the provided constructor
[EL Info]: 2012-05-10 12:01:19.623--ServerSession(17395905)--file:/C:/Users/raviteja.s/Documents/NetBeansProjects/PersistenceApp/src/_PersistenceAppPU logout successful
The two books have been persisted
I dont understand , why there are so many default constructor calls when , there is not one made by me ... ?
Could somebody explain me how the flow is in the sample that i have ?
JPA uses a constructor with no arguments in order to instantiate your Entities, and then bind fields in those entities to the correspondent mapped tables and columns.
Those output you see are the calls that JPA does for you every time it manipulates your entities.