I use Hibernate to control my database and I have 2 tables:
CREATE TABLE IF NOT EXISTS `User`(
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL DEFAULT '',
`account` VARCHAR(255) NOT NULL DEFAULT '',
`password` VARCHAR(255) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
)
CREATE TABLE IF NOT EXISTS `Project` (
`id` INT NOT NULL AUTO_INCREMENT,
`manager` INT NOT NULL,
`name` VARCHAR(255) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
FOREIGN KEY (`manager`) REFERENCES `User`(`id`)
)
And I have done the mapping:
User:
// ... import code
#Entity
#Table
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Column
private String name, account, password;
#OneToMany(mappedBy = "manager")
private List<Project> projects;
public User() {
}
// ... Getter & Setter code
}
Project:
// ... import code
#Entity
#Table
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Column
private String name;
#ManyToOne
#JoinColumn(name = "manager")
private User manager;
public Project () {
}
// ... Getter & Setter code
}
I want to know whether it is possible when I select projects, the project will include its manager data but not have password.
In other ways, I want that each project I get will like this (format as JSON):
{
"id": 0,
"name": "A test project",
"manager": {
"id": 0,
"name": "John Smith"
"accound": "user1",
"password": null
}
}
1. Projection
A projection could be used to limit the fields you want to bring into memory, you could get a projection of all fields except the password.
2. Lazy
Another option can be adding the lazy annotation to the field:
#Basic(fetch = FetchType.LAZY)
#Column(...)
private String password;
3. HQL query
Another way would be to use a direct HQL query and load only the required fields, from this answer.
If you don't want to map this field at all, you could use #Transient annotation:
#Transient
private String password;
If you simply don't want to show this field when you convert the object to a particular JSON representation, then this is a data representation problem, not a ORM mapping problem. You should skip this field when you convert your object to JSON. The implementation depends on what JSON converter you are using. For instance, if you use Jackson, then the #JsonIgnore annotation is what you need.
Related
I am working on a school project, and I am having trouble with joining tables so I can display output in JSP file using JSTL. I will provide all necessary code. I know that I need to connect entities somehow, but I don't know how.
SQL:
CREATE TABLE IF NOT EXISTS `totelegram`.`contacts` (
`id` INT NOT NULL AUTO_INCREMENT,
`first_name` VARCHAR(45) CHARACTER SET 'utf8' COLLATE 'utf8_unicode_ci' NOT NULL,
`last_name` VARCHAR(45) CHARACTER SET 'utf8' COLLATE 'utf8_unicode_ci' NOT NULL,
`phone_number` VARCHAR(45) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `id_UNIQUE` (`id` ASC),
UNIQUE INDEX `phone_number_UNIQUE` (`phone_number` ASC))
ENGINE = InnoDB;
CREATE TABLE IF NOT EXISTS `totelegram`.`messages` (
`id_message` INT NOT NULL AUTO_INCREMENT,
`message` VARCHAR(2000) CHARACTER SET 'utf8' COLLATE 'utf8_unicode_ci' NOT
NULL,
`time` VARCHAR(45) NOT NULL,
`contacts_id` INT NOT NULL,
PRIMARY KEY (`id_message`),
UNIQUE INDEX `id_message_UNIQUE` (`id_message` ASC),
INDEX `fk_messages_contacts_idx` (`contacts_id` ASC),
CONSTRAINT `fk_messages_contacts`
FOREIGN KEY (`contacts_id`)
REFERENCES `totelegram`.`contacts` (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
Contacts.java
#Entity(name = "contacts")
public class Contacts implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
#javax.persistence.Column(name = "first_name")
private String firstName;
#javax.persistence.Column(name = "last_name")
private String lastName;
#javax.persistence.Column(name = "phone_number")
private String phoneNumber;
...getters/setters, constructor, toString...
Messages.java
#Entity(name = "messages")
public class Messages implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#javax.persistence.Column(name = "id_message")
private int id;
private String message;
private String time;
#javax.persistence.Column(name = "contacts_id")
private int contactsId;
...getters/setters, constructor, toString...
MessagesRepository.java
public interface MessagesRepository extends JpaRepository<Messages, Integer> {
//custom query which will output this
//SELECT b.message, b.time, b.contacts_id, a.first_name, a.last_name FROM messages AS b INNER JOIN contacts as A ON (b.contacts_id=a.id) ORDER BY time ASC;
public List<Messages> findAll();
}
I hope I was clear. Thanks to everybody in advance.
As far as i understand, one contact can have N messages and you cannot have a Message without the Contact, right?
Since you have relations between classes, you have to use specific annotations in jpa, for example:
in the Message Class, you should use the #ManyToOne annotation, since you have Many Messages for One Contact. The JoinColumn will input the contacts_id in the Messages Table.
#ManyToOne
#JoinColumn(name = "contacts_id")
private Contacts contact;
in the Contacts Class, you should use #OneToMany annotation, since One Contact has Many Messages. The mappedBy makes a reference in contact at the Message Class.
#OneToMany(mappedBy = "contact")
private List<Messages> messages = new ArrayList<>();
So far you made a Bidirectional reference between Contacts and Messages. Now in your service class, i would recommend you find the Messages through the Contacts, since you cannot have a message without the contact. Its a Repository principle.
Contacts con = repository.findOne(1);
con.getMessages();
btw, sorry for the bad english.
I have a Jpa method like below.
Set<ApplicationMaterialFile> findAllByProcessInstanceIdAndApplicationMaterialIn(String processInstanceId, Collection<ApplicationMaterial> applicationMaterials);
This is the code for call the above Spring Data JPA method. And here will be throw a exception when to call the above Spring Data JPA method.
#Override
public Set<ApplicationMaterialFile> getAllByProcessInstanceIdApplicationMaterialIds(String processInstanceId, Set<Long> applicationMaterialId) {
List<ApplicationMaterial> applicationMaterials = applicationMaterialService.getAllByIdIfNotExistsThenThrowException(applicationMaterialId);
return applicationMaterialFileRepository.findAllByProcessInstanceIdAndApplicationMaterialIn(processInstanceId, applicationMaterials);
}
Here is the ApplicationMaterial entity struct.
/**
*
* #see <a href="https://code.aliyun.com/butterfly-effect/backend/wikis/plans"
* #author himly z1399956473#gamil.com
*/
#Table(name = "application_material_library")
#Entity
#EntityListeners(AuditingEntityListener.class)
#Data
#ToString(exclude = {"university"})
#EqualsAndHashCode(exclude = {"university"})
public class ApplicationMaterial {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false)
private String name;
private String fileFormat;
private String description;
private String templateFileUrl;
#JsonIgnore
#ManyToOne
private University university;
#CreatedDate
#Column(nullable = false)
private Timestamp createdAt;
#LastModifiedDate
#Column(nullable = false)
private Timestamp updatedAt;
}
When to call the Spring Data JPA method findAllByProcessInstanceIdAndApplicationMaterialIn will throw a exception like below
Caused by: java.lang.IllegalArgumentException: Parameter value element
[ApplicationMaterial(id=1, name=sdfsf, fileFormat=doc,
description=dfsdfds, templateFileUrl=fsdfsdf, createdAt=2018-09-26
16:54:09.297, updatedAt=2018-09-26 16:54:23.451)] did not match
expected type [com.hikedu.backend.model.ApplicationMaterial (n/a)]
Can someone please help me?
Update:
The table struct:
-- auto-generated definition
create table application_material_library
(
id bigint auto_increment
primary key,
created_at datetime(6) not null,
description varchar(255) null,
file_format varchar(255) null,
name varchar(255) not null,
template_file_url varchar(255) null,
updated_at datetime(6) not null,
university_id bigint null,
constraint FK9kuemh1kjhyt8u16sqw6i6t59
foreign key (university_id) references universities (id)
);
Spring Data doesn't seem to find your custom method implementation.
Therefore it interprets the method as derived query method and complains that the types of parameters don't match what it expects. The error is strange since it complains, although the types seem to match. But that isn't your problem here. The problem is that the custom method is not found.
This answer describes how to do a custom implementation depending on which version of Spring Data you are using.
I have a really simple domain model: An 'Alert' has one 'Type' and one 'Status'.
This is my schema:
create table `price_alert_status` (
`id` bigint(20) not null,
`status_name` varchar(64) not null,
primary key (`id`),
unique key (`status_name`)
) engine=InnoDB default charset=utf8;
insert into `price_alert_status` values (0, 'INACTIVE');
insert into `price_alert_status` values (1, 'ACTIVE');
create table `price_alert_type` (
`id` bigint(20) not null,
`type_name` varchar(64) not null,
primary key (`id`),
unique key (`type_name`)
) engine=InnoDB default charset=utf8;
insert into `price_alert_type` values (0, 'TYPE_0');
insert into `price_alert_type` values (1, 'TYPE_1');
create table `price_alert` (
`id` bigint(20) not null auto_increment,
`user_id` bigint(20) not null,
`price` double not null,
`price_alert_status_id` bigint(20) not null,
`price_alert_type_id` bigint(20) not null,
`creation_date` datetime not null,
`cancelation_date` datetime null,
`send_periodic_email` tinyint(1) not null,
`price_reached_notifications` tinyint(4) default '0',
`approximate_price_notifications` tinyint(4) null,
`notify` tinyint(1) not null default '1',
primary key (`id`),
constraint `FK_ALERT_TO_ALERT_STATUS` foreign key (`price_alert_status_id`) references `price_alert_status` (`id`),
constraint `FK_ALERT_TO_ALERT_TYPE` foreign key (`price_alert_type_id`) references `price_alert_type` (`id`)
) engine=InnoDB default charset=utf8;
Now, I'm going to show the respective entity classes:
Alert.java:
// imports omitted
#Entity
#Table(name = "price_alert")
#EntityListeners(AuditingEntityListener.class)
#JsonIgnoreProperties(value = {"creationDate"},
allowGetters = true)
public class Alert implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long userId;
private double price;
#OneToOne
#JoinColumn(name = "price_alert_status_id", nullable = false)
private Status status;
#OneToOne
#JoinColumn(name = "price_alert_type_id", nullable = false)
private Type type;
#Column(nullable = false, updatable = false)
#Temporal(TemporalType.TIMESTAMP)
#CreatedDate
private Date creationDate;
#Column(nullable = true)
#Temporal(TemporalType.TIMESTAMP)
private Date cancelationDate;
private boolean sendPeriodicEmail;
#Column(nullable = true)
private byte priceReachedNotifications;
#Column(nullable = true)
private byte approximatePriceNotifications;
private boolean notify;
// getters and setters omitted
}
Status.java:
//imports omitted
#Entity
#Table(name = "price_alert_status")
public class Status implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private Long id;
#Column(name = "status_name")
#NotBlank
private String name;
//getters and setters omitted
}
Type.java:
//imports omitted
#Entity
#Table(name = "price_alert_type")
public class Type implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private Long id;
#Column(name = "type_name")
#NotBlank
private String name;
//getters and setters omitted
}
Repositories:
AlertRepository.java:
//imports omitted
#Repository
public interface AlertRepository extends JpaRepository<Alert, Long> {
}
StatusRepository.java:
//imports omitted
#Repository
public interface StatusRepository extends JpaRepository<Status, Long> {
}
TypeRepository.java:
//imports omitted
#Repository
public interface TypeRepository extends JpaRepository<Type, Long> {
}
Now, the main controller:
AlertController.java:
#RestController
#RequestMapping("/api")
public class AlertController {
#Autowired
AlertRepository alertRepository;
#Autowired
StatusRepository statusRepository;
#Autowired
TypeRepository typeRepository;
#GetMapping("/alerts")
public List<Alert> getAllAlerts() {
return alertRepository.findAll();
}
#PostMapping("/alert")
public Alert createAlert(#Valid #RequestBody Alert alert) {
return alertRepository.save(alert);
}
#GetMapping("/alert/{id}")
public Alert getAlertById(#PathVariable(value = "id") Long alertId) {
return alertRepository.findById(alertId)
.orElseThrow(() -> new ResourceNotFoundException("Alert", "id", alertId));
}
#PutMapping("/alert/{id}")
public Alert updateAlert(#PathVariable(value = "id") Long alertId,
#Valid #RequestBody Alert alertDetails) {
Alert alert = alertRepository.findById(alertId)
.orElseThrow(() -> new ResourceNotFoundException("Alert", "id", alertId));
alert.setApproximatePriceNotifications(alertDetails.getApproximatePriceNotifications());
alert.setCancelationDate(alertDetails.getCancelationDate());
alert.setNotify(alertDetails.isNotify());
alert.setPrice(alertDetails.getPrice());
alert.setPriceReachedNotifications(alertDetails.getPriceReachedNotifications());
alert.setSendPeriodicEmail(alertDetails.isSendPeriodicEmail());
alert.setUserId(alertDetails.getUserId());
// TODO: how to update Status and Type?
Alert updatedAlert = alertRepository.save(alert);
return updatedAlert;
}
#DeleteMapping("/alert/{id}")
public ResponseEntity<?> deleteAlert(#PathVariable(value = "id") Long alertId) {
Alert alert = alertRepository.findById(alertId)
.orElseThrow(() -> new ResourceNotFoundException("Alert", "id", alertId));
alertRepository.delete(alert);
return ResponseEntity.ok().build();
}
}
So, I have two questions:
How can I create an alert, via POST, and associate existing status and type?
For example, this would be my cURL. I'm trying to indicate that I want to associate to this new alert the 'Status' and 'Type' existing objects, passing their respective IDs:
curl -H "Content-Type: application/json" -v -X POST localhost:8080/api/alert -d '{"userId": "1", "price":"20.0", "status": {"id": 0}, "type": {"id": 0}, "sendPeriodicEmail":false,"notify":true}'
Like the first question, how can I update an Alert, associating new existing 'Status' and 'Type' objects?
Thanks!
I think there is no out-of-the-box way to achieve this with a single POST request. The approach I see used most of the time is making an initial request to create the Alert, and subsequent requests to associate Status and Type.
You could take a look at how Spring Data Rest approaches the problem here:
https://reflectoring.io/relations-with-spring-data-rest/
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#repository-resources.association-resource
I'm not the biggest fan of Spring Data Rest though, since it forces some things (like hateoas) down your throat
,but you can easily implement the same approach manually.
You could argue that it's overkill to have separate calls to set the status and type of an alert, being both actually part of the alert, and I may agree actually. So if you don't mind slightly deviating from the rigidity of what people mostly call REST APIs (but are more like CRUD interfaces exposing your data model), it could make sense to take an AlertDto (with status and type ids) in your alert creation endpoint, retrieve status and type with these ids and create the Alert object you will eventually store.
Having said all of the above, I would avoid having tables for Status and Type if all they have is a name. I would have these names in the Alert itself and no relationships at all. Yes it may occupy more space on the database, but disk space is hardly a problem nowadays, and I'm guessing status and type are usually short strings.
I admit I am specially biased against this id-name lookup table pattern because we have dozens of these in one of our projects at work and they do nothing but generate a lot of useless code and complicate the DB schema.
I have a class 'Audit' that will hold the following details about an entity:
Time Created
Created by which application user
Time Updated
Updated by which application user
On the database, these fields are stored on the same table as the entity attributes. For example,
USER table:
CREATE TABLE USERS (
id BIGINT NOT NULL AUTO_INCREMENT,
display_name VARCHAR(30) NOT NULL,
active BOOLEAN NOT NULL DEFAULT FALSE,
created_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_user BIGINT NOT NULL DEFAULT 0,
updated_time TIMESTAMP NOT NULL DEFAULT 0,
updated_user BIGINT NOT NULL DEFAULT 0,
PRIMARY KEY (id)
);
USER Class:
#Entity(name="USER")
#Table(name="USERS")
public class User implements Audited {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="ID", nullable=false, updatable=false)
private long id;
#Column(name="DISPLAY_NAME", nullable=false)
#NotNull
#Size(min=5, max=30)
private String displayName;
#Column(name="ACTIVE", nullable=false)
#NotNull
private boolean active;
private Audit audit;
}
AUDIT Class:
public class Audit {
private Date createdTime;
private User createdByUser;
private Date updatedTime;
private User updatedByUser;
}
On each entity table, the audit fields will be named the same. What is the best approach in mapping these fields?
The best way would be to make the Audit class as #Embeddable entity and use it in your all entities as an #Embedded property, which is almost what your are doing currently.
Using hibernate and mysql 5.5, I am trying to persist String value in TEXT type column of database table.
Tired to set String value in the mentioned column and tried to persist the data.But i am getting following exception. I have generated Entity class using Netbeans 8.0.
Exception:-
FATAL: JSF1073: javax.faces.FacesException caught during processing of INVOKE_APPLICATION 5 : UIComponent-ClientId=, Message=/addNewCategory.xhtml #30,151 actionListener="#{categoryBean.addCategoryAction}": java.lang.AbstractMethodError: com.mysql.jdbc.ServerPreparedStatement.setCharacterStream(ILjava/io/Reader;J)V
FATAL: /addNewCategory.xhtml #30,151 actionListener="#{categoryBean.addCategoryAction}": java.lang.AbstractMethodError: com.mysql.jdbc.ServerPreparedStatement.setCharacterStream(ILjava/io/Reader;J)V
javax.faces.FacesException: /addNewCategory.xhtml #30,151 actionListener="#{categoryBean.addCategoryAction}": java.lang.AbstractMethodError: com.mysql.jdbc.ServerPreparedStatement.setCharacterStream(ILjava/io/Reader;J)V
at com.sun.faces.lifecycle.InvokeApplicationPhase.execute(InvokeApplicationPhase.java:89)
CREATE SQL:-
CREATE TABLE `oc_category_description` (
`category_id` int(11) NOT NULL,
`language_id` int(11) NOT NULL,
`name` varchar(255) NOT NULL,
`description` text NOT NULL,
`meta_description` varchar(255) NOT NULL,
`meta_keyword` varchar(255) NOT NULL,
PRIMARY KEY (`category_id`,`language_id`),
KEY `name` (`name`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = #saved_cs_client */;
EntityClass
#Entity
#Table(name = "oc_category_description")
#XmlRootElement
public class OcCategoryDescription implements Serializable {
private static final long serialVersionUID = 1L;
#Basic(optional = false)
#NotNull
#Lob
#Size(min = 1, max = 65535)
#Column(name = "description")
private String description;
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
//Constructors, setters, getters, equals and hashcode
}
Before raising question I went through following links which were of very little help.
JPA: how do I persist a String into a database field, type MYSQL Text
JPA and PostqreSQL: long string persistence
Issue persisting long strings with Hibernate
I also tried to persist data by removing #Lob, it saving data as "org.hibernate.engine.jdbc.BlobProxy#11e84b60"
I tried using #Column(columnDefinition = "TEXT") instead of #Lob, again its giving same result. "org.hibernate.engine.jdbc.BlobProxy#11e84b60"
I tried #Type(type="text")instead of #Lob, again its giving same result. "org.hibernate.engine.jdbc.BlobProxy#11e84b60"
remove the #Lob and #Size annotations, and use #Type(type="text")
instead
WORKED. Thanks #Maurice and #Funtik