As part of my program, I'm using relational tables which hold information such as - user role, job category etc. Each table may have slightly differing fields - for example:
User Role Table has the following fields:
id (auto-generated)
role (eg Planner, Admin etc)
role_description (description of above role)
enabled (toggle this role on/off)
Job Category Table:
id (auto-generated)
category (eg Service, Maintenance etc)
category_description (description of above)
category_group (categories are grouped into management areas)
...
enabled (toggle category on/off)
The lists can be changed by the end user so I need to provide an admin section to enable new roles/categories to be added.
I had thought of creating a routine where I pass the entity class of the role/category etc and have it generate an array which can be used to populate the admin section but have only been able to do this for the 1st two columns - eg id/role or id/category.
With the fields differing between each entity, is there a way that I can do this? Or will I have to create a method in each of the entities - such as getRoleList and getCategoryList etc?
Thanks.
After a bit of experimenting, I've decided to implement this in the following way.
I've added methods to my database helper class that will read the list and populate an array. I'll have to create a separate method for each entity but I've decided this would be necessary due to the differences between the classes.
I'm not 100% sure that this is the most efficient way of accomplishing this but it does what I need (for now).
One of the methods:
public static UserRole[] getUserRoleList(String order, Boolean reverseOrder) throws SQLException {
Session session = openSession();
List<UserRole> list;
if (!reverseOrder) {
// obtain list and sort by provided field in ascending order
list = session.createCriteria(UserRole.class).addOrder(Order.asc(order)).list();
} else {
// sort descending
list = session.createCriteria(UserRole.class).addOrder(Order.desc(order)).list();
}
// return UserRole[]
return list.toArray((UserRole[]) Array.newInstance(UserRole.class, list.size()));
}
The rest of the methods will be pretty much identical (substituting the entity/class names). The only difference would be adding another argument for some entities (enabled Boolean, so I can return only items in the list which are enabled).
Edit:
Since posting the above, I changed my mind and moved to a generic method to obtain lists, passing in the entity class as below:
public static List getList(Class entity, String order, Boolean reverseOrder, Boolean enabled) {
// stripped for brevity...
list = session.createCriteria(entity)
.add(Restrictions.eq("enabled", true))
.addOrder(Order.asc(order)).list();
// stripped more...
return list;
}
Casting when calling the method:
List<User> userList = DatabaseHelper.getList(User.class);
Related
I know a similar question has been asked before but I still don't know how to go about it.
I would like to create a business class for the Transfer and the Shares tables. This will make it easier for me to code.
Example:
When a user deletes his account I must delete his lists only if his lists have not been transferred or shared.
So I'm tempted to do this:
private void removeUserIdFromTransferredLists(int userId){
List<Transfer> list = findListsWhereUserIsOwnerOrReceiver(userId);
list.forEach(l -> {
if(l.getOwnerId() == userId){
//update ownerId to null
} else if(l.getReceiverId() == userId){
//update receiverId to null
}
});
}
In my modeling classes, it's written that we don't make business classes for tables that only contain foreign keys.
In that particular case can I create a business class?
public List<Post> getPosts(String city) {
// if City parameter is presented(When searched by an user)
if (!Strings.isNullOrEmpty(city)) {
return postRepository.findAllByCityOrderByIdDesc(city).stream().map(obj -> {
obj.getUser().setPassword("");
return obj;
}).collect((Collectors.toList()));
} else {
return postRepository.findAllByOrderByIdDesc().stream().map(obj -> {
obj.getUser().setPassword("");
return obj;
}).collect((Collectors.toList()));
}
}
I tried to change object values with the stream map function after fetching data from the DB, and the values in the DB changed too.
are they two connected?
The fact that you're using a stream to operate on the values in the list is entirely irrelevant:
JPA is a "magic" technology that specifically provides the feature of automatically persisting changes that you make to JPA entity objects.
The stream() just takes an existing collection of objects and operates on them.
You are modifying the objects in map, which is not recommended in general. You could detach the users from the EntityManager, but...
you should be mapping these to DTOs rather than presenting the entity objects to your top-level API layer, and
when you have chunks like this that need to be isolated, you should consider separating your database model so that the "public profile" part of the user and the authentication part aren't grouped together.
I'm trying to implement a method for updating a database record. So far, I created this one:
public Optional<User> update(final Integer id,final UpdateUserDto dto) {
userRepository.findById(id).ifPresent((user -> {
user.setShop((dto.getShopId() == null) ? null : shopRepository.findById(dto.getShopId())
.orElseThrow(ShopNotFoundException::new));
user.setUsername(dto.getUsername());
user.setPassword(passwordEncoder.encode(dto.getPassword()));
user.setRoles(Arrays.asList(
roleRepository.findByRoleName(dto.getRole()).orElseThrow(RoleNotFoundException::new)
));
}));
return userRepository.findById(id);
}
But now I added two more fields to my user entity (activated, productAllowed) and I must enhance my update method to make them updatable. I can do that, but I have other entities also and if I change them it will be a lot of maybe boilerplate code.
Is there any kind of best practice to do this in a better way, or I just need to keep setting all the fields manually?
I was also thinking about reflection, but in that case I have a few fields that cannot be copied exactly from the DTO to the entity (e.g. the shop field, which is queried from database, or role field).
And I also don't think that another query for returning the new object is effective, but although I set the properties in a service layer, the original findById()'s returned user is wrapped inside an Optional, so I don't think it will be updated.
Thank you in advance.
I am using Spring JDBC and I am a bit unsure on how to work with multiple one-to-many relations (or many-to-many). In this case I am injecting a repository into one of my resultsetextractors so that I can retrieve its associations. Is this the way to do it? Is it bad? Are there other better ways?
Note: I have left out the injection of repository
public class SomeResultSetExtractor implements ResultSetExtractor {
public Object extractData(ResultSet rs) throws SQLException, DataAccessException {
List result = new LinkedList();
while (rs.next()) {
SomeObject object = new SomeObject(rs.getString(1), rs.getLong(2));
result.add(object);
List<AnotherObject> otherObjects = anotherRepository.findAllById(object.getId);
object.setOtherObjects(otherObjects);
// and so on
}
return result;
}
}
Okey so after reading Dmytro Polivenok answer I have changed to RowMapper interface instead and I am currently using the other repositories to populate all associations like I show in my example. Is this a good way of doing it?
I think a good practice for Spring JDBC and SQL queries in general is to use one query for each entity.
E.g. assume this model:
Customer (customerId, name, age, ...)
Address (customerId, type, street, city, ...)
PaymentOption (customerId, cardnumber, cardtype, ...)
Customer 1---* Address
Customer 1---* PaymentOption
I would build 3 queries, 3 Daos, 3 ResultSetExtractors/RowcallbackHandlers:
CustomerDao with readCustomerData(Customer or List)
AddressDao with readAddressForCustomer(Customer or List)
PaymentOptionDao with readPaymentOptionsForCustomer(Customer or List)
If you would bake this in 1 query, you would have to build some logic to revert the cartasian product.
I.e. if the customer has 3 addresses and 2 payment options the query would return 6 rows.
This gets quite hard, if Address or PaymentOption does not have an own primary key.
For many to many:
Customer * --recommends-- * Product
I would probably build:
CustomerDao.readRecommendationsAndProductKeys
getDistinctListOfProductKeysFromRecommendations
ProductDao.readProducts
replaceProductKeysByProductsOnRecommendations
Like this you could reuse ProductDao.readProducts for
Customer * --buys-- * Product or
ProductGroup 1---* Product
I think that your code will work, but the concern here is about usage of ResultSetExtractor which is mainly for JDBC framework itself, and for most cases documentation recommends to use RowMapper.
So alternative approach would be to have method in your DAO that selects and maps parent object. Then for each object to invoke other Repository or private method that selects and maps child objects, and then to link child objects with parents based on your relationship type (one-directional or bidirectional). This approach may also allow you to control whether you want to load child objects or not.
For example, you may check Spring PetClinic application which has SimpleJdbcClinic class
If you can use other frameworks, you may consider mybatis, it is more about mapping and allows you to control your SQL code.
We have the following JPA class:
#Entity
class Supplier {
// ... id property etc.
#OneToMany
#OrderBy("someProperty")
private List<Region> regions;
}
This works fine in the normal case. However, we have some multi-lingual data where the values are stored in properties like nameEn, nameDe, nameZh. The exact property to use depends on the logged in user. For example, a German speaking user should see the regions as if it had been annotated with #OrderBy("nameDe").
How can I achieve this?
I am aware I could sort the collection in my code after it has been loaded, but this makes pagination of the results quite difficult.
You could sort them in java. Possibly in a getter:
List<Region> getRegions(){
sorted = new List<Regions>(regions);
Collections.sort(sorted, new RegionComparator(getUserLanguage()));
return sorted;
}