MySql insert data in wrong order from JPA - mysql

I am trying to import the data from excel ile to different tables in my DB.
The import is done , the only problem is that some of the data is in the wrong order.
This is what my excel file loks like.
And this is what the data in my Bd lokks like.
My service file has this method:
#Override
public List<Task> getTasksFromExcel(MultipartFile files) throws IOException {
List<Task> taskList = new ArrayList<>();
XSSFWorkbook workbook = new XSSFWorkbook(files.getInputStream());
XSSFSheet worksheet = workbook.getSheetAt(0);
String a = "A";
for (int index = 0; index <= worksheet.getPhysicalNumberOfRows(); index++) {
if (index > 1) {
Task task = new Task();
Lot lot = new Lot();
String ref = a + index;
CellReference cr = new CellReference(ref);
XSSFRow row = worksheet.getRow(cr.getRow());
String lotName = row.getCell(0).getStringCellValue();
Lot existingLot = lotRepository.findByName(lotName);
if (existingLot == null) {
lot.setName(lotName);
lotRepository.save(lot);
} else {
lot = existingLot;
}
;
task.setName(row.getCell(1).getStringCellValue());
String email = row.getCell(2).getStringCellValue();
Collaborator collab = collaboratorRepository.findByEmail(email);
task.setCollaborator(collab);
List<Double> iC = new ArrayList<>();
for (int i = 3; i < 6; i++) {
iC.add((Double) row.getCell(i).getNumericCellValue());
}
Set<Double> charge = new HashSet<Double>();
charge.addAll(iC);
task.setInitialCharge(charge);
task.setLot(lot);
taskList.add(task);
taskRepository.save(task);
}
}
return taskList;
}
And for the entity definition I go tthis.
#Entity
#Table(name = "task")
public class Task {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
private String description;
private Date assignment;
private Date deadline;
#ElementCollection
private Set<Double> initialCharge=new HashSet<Double>();
#Column(columnDefinition = "varchar(32)")
#Enumerated(EnumType.STRING)
private Status status = Status.TODO;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "lot_id")
private Lot lot;
#ManyToOne()
#JoinColumn(name = "collaborator_id")
private Collaborator collaborator;

You mapped this differently then your excel spreadsheet, and so lose the S1,S2,S3 column name/ordering you had for the single Task row, and seemed to have assumed that the set positional would be consistent, giving you S1-S3 for free. It does not.
Normalizing this out to allow expanding lists of charges and still having an order would mean adding a positional column to the task_initial_charges table. JPA will populate this column behind the scenes if you simply annotate your element collection with the OrderColumn to specify it:
The task_initial_charge needs s1,s2,s3 columns so that a single task_id has 3 positional columns, or you need another column in there to allow writing out the position within your initialCharge Set.
#ElementCollection
#OrderColumn
private Set<Double> initialCharge=new HashSet<Double>();
The order of the initialCharge set when the entity is persisted will then be stored in the database, and should be used when fetching the entity.

Related

How To Insert Data with Spring Data JPA Relationships

hopefully this is an easy question, but I'm pretty new with JPA and having difficulty determining how to format a JSON POST request body that is sent to a Spring API. I have two entities, Product and Barcode with the following relationship:
One Product can have many barcodes that point to it (OneToMany)
One Barcode can point to only one Product (OneToOne)
The relationship is defined by a product_id column in the Barcode table.
Relationship definition in Barcode entity:
#OneToOne // one barcode relates to one product
private ProductEntity product;
The relationship definition in Product:
#OneToMany(cascade = CascadeType.ALL)
private List<BarcodeEntity> barcodes = new ArrayList<>();
My question is, how can I do a JSON POST request to insert a Barcode that is related to a Product already in the database? Would I need to pass the entire Product entity or is there a way to just pass the product_id alone?
How I would like to create a new Barcode entry when the Product already exists:
{
"barcode": "string",
"barcodeStatus": "string",
"codeStandard": "string",
"product": 1,
"title": "string",
"unitQuantity": 0
}
Instead of having to do the following, which I believe will result in an error because the product already exists:
{
"barcode": "string",
"barcodeStatus": "string",
"codeStandard": "string",
"product": {
productInfo: "...",
....,
},
"title": "string",
"unitQuantity": 0
}
Essentially, I'm trying to figure out how you can insert a new entity and define its relationship to another entity that is already present in the database. I'm sure I'm over complicating it.
Endpoint in Product controller:
#PostMapping(produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE })
public ProductRest createProduct(#Valid #RequestBody ProductRequestModel productDetails) throws Exception {
ProductRest returnValue = new ProductRest();
ModelMapper modelMapper = new ModelMapper();
ProductDto productDto = modelMapper.map(productDetails, ProductDto.class);
ProductDto createdProduct = productService.createProduct(productDto);
returnValue = modelMapper.map(createdProduct, ProductRest.class);
return returnValue;
}
createProduct method in Product service file:
public ProductDto createProduct(ProductDto product) {
if (productRepo.findByTitle(product.getTitle()) != null)
throw new ServiceException("Record with matching title already exists");
// Set product id for each barcode
for (int i = 0; i < product.getBarcodes().size(); i++) {
ProductBarcodeDto barcode = product.getBarcodes().get(i);
barcode.setProduct(product);
product.getBarcodes().set(i, barcode);
}
ModelMapper modelMapper = new ModelMapper();
ProductEntity productEntity = modelMapper.map(product, ProductEntity.class);
ProductEntity storedProductDetails = productRepo.save(productEntity);
ProductDto returnValue = modelMapper.map(storedProductDetails, ProductDto.class);
return returnValue;
}
Fields in ProductDto (defines getters/setters and empty constructor; just not shown):
public class ProductDto {
private long id;
private List<BarcodeDto> barcodes;
private String title;
private String description;
private String SKU;
private ProductVariationDto variation;
private double cost;
private double retailPrice;
private LocalDate launchDate;
private LocalDate discontinueDate;
private String discontinueReason;
private String salesChannel;
private LabelDto label;
private int secondaryStockLevel;
private int primaryStockLevel;
private LocalDateTime modifiedDate;
private LocalDateTime createdDate;
private String productStatus;
private List<SupplierDto> suppliers;
}
I had it set up so that ProductRequestModel expected a BarcodeEntity. Is this the correct or should I change it to expect just an integer value for the Barcode ID?
createBarcode endpoint in Barcode Controller:
#PostMapping(produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE })
public BarcodeRest createBarcode(#Valid #RequestBody BarcodeRequestModel barcodeDetails) throws Exception {
BarcodeRest returnValue = new BarcodeRest();
ModelMapper modelMapper = new ModelMapper();
BarcodeDto barcodeDto = modelMapper.map(barcodeDetails, BarcodeDto.class);
BarcodeDto createdBarcode = barcodeService.createBarcode(barcodeDto);
returnValue = modelMapper.map(createdBarcode, BarcodeRest.class);
return returnValue;
}
createBarcode method implementation:
public BarcodeDto createBarcode(BarcodeDto barcode) {
ModelMapper modelMapper = new ModelMapper();
if (barcodeRepo.findByBarcode(barcode.getBarcode()) != null)
throw new ServiceException("Barcode value already exists.");
BarcodeEntity barcodeEntity = modelMapper.map(barcode, BarcodeEntity.class);
BarcodeEntity storedBarcode = barcodeRepo.save(barcodeEntity);
BarcodeDto returnValue = modelMapper.map(storedBarcode, BarcodeDto.class);
return returnValue;
}
Firstly, there seems to be an issue with the Database Schema Mapping:
One Product : Many Barcodes (One to Many) [NO ISSUES]
Many Barcodes : One Product (Many to One, instead of One to One) [HAS ISSUES]
Secondly, yes you can use just the 'product_id' to update the barcode. It'd be better if you post the controller code once. Would be easier to find any issues with that.

Problem with persisting a list of objects into MYSQL database using spring boot

I'm building a microservices architecture using spring boot. Now I run into a weird problem, which I don't know how to solve...
On of the services is a SongService, which provides endpoints for songs as well as for songLists.
My data model looks like this:
The POST-Method in the SongListController works in this way: A User can post a new SongList as Json by sending a POST-Request to http://localhost:8082/rest/v1/songlists/<userId(int)>. The SongService gets the User, sets the listOwner=userName and stores the SongList into the database using JpaRepositoriy.
My Entity Class looks like this:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
#Entity
public class SongList {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "songlist_id", updatable = false, nullable = false, unique = true)
private int songListId;
#Column(length = 45)
private String OwnerId;
#Column(length = 45, nullable = false)
private String name;
#Column(nullable = false)
private boolean isPrivate;
#ManyToMany()
#JoinTable(name = "songlist_song",joinColumns ={ #JoinColumn( name = "songlist_id",
referencedColumnName = "song_id")}, inverseJoinColumns ={ #JoinColumn( name ="song_id", referencedColumnName = "song_id")})
private List<Song> songs;
}
If I call the POST-Method, as described above, the SongsList gets persisted inside the songs_list table inside my MYSQL database, but without any songs - means, the songlist_song table stays empty. And if I GET the SongsList, I get it back without songs inside.
This is the corresponding code in my SongsListController:
#PostMapping(value = "/{userId}", consumes = "application/json")
public ResponseEntity<String> postNewSongList(#PathVariable(value = "userId") Integer userId,#RequestBody SongList songList) {
if (songList.getName() == null || songList.getName().equals("") || songList.getName().trim().isEmpty())
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
int songListId = songListService.saveNewSongList(userId, songList);
if (songListId == -1)
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
HttpHeaders header = new HttpHeaders();
header.set("Location", "/rest/v1/songlists/" + songListId);
return ResponseEntity.ok().headers(header).body("");
}
And this is the saveNewSongList-Method in my SongListService:
#Override
public int saveNewSongList(int userId, SongList songList) {
User user = restTemplate.getForObject("http://localhost:8081/rest/v1/user/" + userId, User.class);
if (user == null)
return -1;
if (songList.getSongs() != null) {
for(Song song : songList.getSongs())
if (songRepository.findById(song.getId()).isEmpty())
return -1;
}
songList.setOwnerId(user.getUserId());
SongList savedSongList = songListRepository.save(songList);
return savedSongList.getSongListId();
}
THERE'S NO ERROR and that's why I don't know how to debug this...
I think, I did something wrong with this #JoinTable-Annotation, but cannot see what.
I'd be very glad, if someone could give me a hint on how to solve this...
Thank you very much!

JPA Specification multiple join based on foreignkey

I have following relationships between three objects
public class ProductEntity {
#Id
private int id;
#OneToMany(mappedBy = "productEntity",
fetch = FetchType.LAZY)
private List<ProductInfoEntity> productInfoEntityList = new ArrayList<>();
#Column(name = "snippet")
private String snippet;
}
public class ProductInfoEntity {
#Id
private int id;
#ManyToOne
#JoinColumn(name = "product_id")
private ProductEntity productEntity;
#ManyToOne
#JoinColumn(name = "support_language_id")
private SupportLanguageEntity supportLanguageEntity;
}
public class SupportLanguageEntity {
#Id
private int id;
#Column("name")
private String name;
}
And this is actual database design
Then, I'd like to make a specification to query as followings:
select * from product_info
where product_id = 1
and support_language_id = 2;
I am also using annotation for the specification which means that I use ProductEntity_, ProductInfoEntity_ and so on.
Can you please give me a full working code for the specification for query mentioned above?
Thank you guys
To use Specification your ProductInfoEntityRepository have to extend JpaSpecificationExecutor
#Repository
public interface ProductInfoEntityRepository
extends JpaRepository<ProductInfoEntity, Integer>, JpaSpecificationExecutor<ProductInfoEntity> {
}
As far as I understand you use JPA metamodel. So then
#Autowired
ProductInfoEntityRepository repository;
public List<ProductInfoEntity> findProductInfoEntities(int productId, int languageId) {
return repository.findAll((root, query, builder) -> {
Predicate productPredicate = builder.equal(
root.get(ProductInfoEntity_.productEntity).get(ProductEntity_.id), // or root.get("productEntity").get("id")
productId);
Predicate languagePredicate = builder.equal(
root.get(ProductInfoEntity_.supportLanguageEntity).get(SupportLanguageEntity_.id), // or root.get("supportLanguageEntity").get("id")
languageId);
return builder.and(productPredicate, languagePredicate);
});
}
If you want to make specifications reusable you should create utility class contains two static methods productIdEquals(int) and languageIdEquals(int).
To combine them use Specifications(Spring Data JPA 1.*) or Specification(since Spring Data JPA 2.0)
select * from product_info where product_id = 1 and support_language_id = 2;
Should work as written. But the only thing useful will be comment.
Perhaps you want the rest of the info in all three tables?
SELECT pi.comment, -- list the columns you need
p.snippet,
sl.name
FROM product AS p -- table, plus convenient "alias"
JOIN product_info AS pi -- another table
ON p.id = pi.product_info -- explain how the tables are related
JOIN support_language AS sl -- and another
ON pi.support_language_id = sl.id -- how related
WHERE p.snippet = 'abc' -- it is more likely that you will start here
-- The query will figure out the rest.
From there, see if you can work out the obfuscation provided by JPA.

The most efficient way to store photo reference in a database

I'm currently looking to store approximately 3.5 million photo's from approximately 100/200k users. I'm only using a mysql database on aws. My question is in regards to the most efficient way to store the photo reference. I'm only aware of two ways and I'm looking for an expert opinion.
Choice A
A user table with a photo_url column, in that column I would build a comma separated list of photo's that both maintain the name and sort order. The business logic would handle extracting the path from the photo name and append photo size. The downside is the processing expense.
Database example
"0ea102, e435b9, etc"
Business logic would build the following urls from photo name
/0e/a1/02.jpg
/0e/a1/02_thumb.jpg
/e4/35/b9.jpg
/e4/35/b9_thumb.jpg
Choice B - Relational Table joined on user table with the following fields. I'm just concerned I may have potential database performance issues.
pk
user_id
photo_url_800
photo_url_150
photo_url_45
order
Does anybody have any suggestions on the better solution?
The best and most common answer would be: choice B - Relational Table joined on user table with the following fields.
id
order
user_id
desc
photo_url_800
photo_url_150
photo_url_45
date_uploaded
Or a hybrid, wherein, you store the file names individually and add the photo directory with your business logic layer.
My analysis, your first option is a bad practice. Comma separated fields are not advisable for database. It would be difficult for you to update these fields and add description on it.
Regarding the table optimization, you might want to see these articles:
Optimizing MyISAM Queries
Optimizing InnoDB Queries
Here is an example of my final solution using the hibernate ORM, Christian Mark, and my hybrid solution.
#Entity
public class Photo extends StatefulEntity {
private static final String FILE_EXTENSION_JPEG = ".jpg";
private static final String ROOT_PHOTO_URL = "/photo/";
private static final String PHOTO_SIZE_800 = "_800";
private static final String PHOTO_SIZE_150 = "_150";
private static final String PHOTO_SIZE_100 = "_100";
private static final String PHOTO_SIZE_50 = "_50";
#ManyToOne
#JoinColumn(name = "profile_id", nullable = false)
private Profile profile;
//Example "a1d2b0" which will later get parsed into "/photo/a1/d2/b0_size.jpg"
//using the generatePhotoUrl business logic below.
#Column(nullable = false, length = 6)
private String fileName;
private boolean temp;
#Column(nullable = false)
private int orderBy;
#Temporal(TemporalType.TIMESTAMP)
private Date dateUploaded;
public Profile getProfile() {
return profile;
}
public void setProfile(Profile profile) {
this.profile = profile;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public Date getDateUploaded() {
return dateUploaded;
}
public void setDateUploaded(Date dateUploaded) {
this.dateUploaded = dateUploaded;
}
public boolean isTemp() {
return temp;
}
public void setTemp(boolean temp) {
this.temp = temp;
}
public int getOrderBy() {
return orderBy;
}
public void setOrderBy(int orderBy) {
this.orderBy = orderBy;
}
public String getPhotoSize800() {
return generatePhotoURL(PHOTO_SIZE_800);
}
public String getPhotoSize150() {
return generatePhotoURL(PHOTO_SIZE_150);
}
public String getPhotoSize100() {
return generatePhotoURL(PHOTO_SIZE_100);
}
public String getPhotoSize50() {
return generatePhotoURL(PHOTO_SIZE_50);
}
private String generatePhotoURL(String photoSize) {
String firstDir = getFileName().substring(0, 2);
String secondDir = getFileName().substring(2, 4);
String photoName = getFileName().substring(4, 6);
StringBuilder sb = new StringBuilder();
sb.append(ROOT_PHOTO_URL);
sb.append("/");
sb.append(firstDir);
sb.append("/");
sb.append(secondDir);
sb.append("/");
sb.append(photoName);
sb.append(photoSize);
sb.append(FILE_EXTENSION_JPEG);
return sb.toString();
}
}

How to handle unidirectional many-to-many relations with Ebean

I have a problem with Ebean. I have the usual Objects PsecUser, PsecRoles and PsecPermission.
A user can have many Permissions or Roles and a Role can have many Permission.
Here the code (extract):
#Entity
public class PsecPermission {
#Id
#GeneratedValue
private Long id;
#Column(unique=true, nullable=false)
private String name;
#Column(nullable=false)
private String type = PsecBasicPermission.class.getName();
#Column(nullable=false)
private String target;
#Column(nullable=false)
private String actions;
}
#Entity
public class PsecRole {
#Id
#GeneratedValue
private Long id;
#Column(unique=true, nullable=false)
private String name;
#Temporal(TemporalType.TIMESTAMP)
private Date lastUpdate;
#ManyToMany(fetch=FetchType.EAGER)
private List<PsecPermission> psecPermissions;
private boolean defaultRole = false;
}
I wrote the following helper-method:
public PsecRole createOrUpdateRole(String name, boolean defaultRole, String... permissions) {
PsecRole result = server.find(PsecRole.class).
where().eq("name", name).findUnique();
if (result == null) {
result = new PsecRole();
result.setName(name);
}
final List<PsecPermission> permissionObjects = server.find(PsecPermission.class).
where().in("name", (Object[])permissions).findList();
result.setPsecPermissions(permissionObjects);
result.setDefaultRole(defaultRole);
final Set <ConstraintViolation <PsecRole>> errors =
Validation.getValidator().validate(result);
if (errors.isEmpty()) {
server.save(result);
server.saveManyToManyAssociations(result, "psecPermissions");
} else {
log.error("Can't save role: " + name +"!");
for (ConstraintViolation <PsecRole> constraintViolation : errors) {
log.error(" " + constraintViolation);
}
}
return result;
}
and try the following test:
#Test
public void testCreateOrUpdateRole() {
String[] permNames = {"Test1", "Test2", "Test3"};
List <PsecPermission> permissions = new ArrayList <PsecPermission>();
for (int i = 0; i < permNames.length; i++) {
helper.createOrUpdatePermission(permNames[i], "target"+ i, "actions" +i);
PsecPermission perm = server.find(PsecPermission.class).where().eq("name", permNames[i]).findUnique();
assertThat(perm.getTarget()).isEqualTo("target" + i);
assertThat(perm.getActions()).isEqualTo("actions" + i);
permissions.add(perm);
}
PsecRole orgRole = helper.createOrUpdateRole(ROLE, false, permNames);
testRole(permNames, orgRole);
PsecRole role = server.find(PsecRole.class).where().eq("name", ROLE).findUnique();
testRole(permNames, role);
}
private void testRole(String[] permNames, PsecRole role) {
assertThat(role).isNotNull();
assertThat(role.getName()).isEqualTo(ROLE);
assertThat(role.isDefaultRole()).isEqualTo(false);
assertThat(role.getPermissions()).hasSize(permNames.length);
}
Which fails if it checks the number of permissions at the readed role. It's always 0.
I looked into the database and found that psec_role_psec_permission is alway empty.
Any idea what's wrong with the code?
You can get a pure Ebean-example from https://github.com/opensource21/ebean-samples/downloads it uses the eclipse-plugin from ebean.
There are two solutions for this problem:
Simply add cascade option at PsceRole
#ManyToMany(fetch=FetchType.EAGER, cascade=CascadeType.ALL)
private List<PsecPermission> psecPermissions;
and remove server.saveManyToManyAssociations(result, "psecPermissions"); you find it in the cascade-solution-branch.
The cleaner solution, because you don't need to define cascase- perhaps you don't want it:
Just don't replace the list, just add your entries to the list. Better is to add new and remove old one. This mean in createOrUpdateRole:
result.getPsecPermissions().addAll(permissionObjects);
instead of
result.setPsecPermissions(permissionObjects);