how to print an alias attribute in JSP file using #Query annotation - spring-data-jpa

This is rows in database.
group userid
1 a
2 b
1 c
3 d
I want to query using #query annotation about
#Query(value ="SELECT *, count(*) as cnt FROM user group by body order by cnt DESC", nativeQuery = true)
List<User> getGroupRanking();
and, i expect to result about
group cnt
1 2
2 1
3 d
but List<User> collection is not accept to 'cnt' attribute which was alias.
another reason not to accept, User defined class was not containing 'cnt' attribute. i was defined 'cnt' attribute in User Class like following code.
public class User{
#Column(name ="group")
private String group;
..
private Integer cnt;
public Integer getCnt(){ return cnt; }
public void setCnt(Integer cnt){ this.cnt = cnt; }
}
Although I will prevent to update the schema, The above code update the schema. because
appConfig.xml configuration was set like this
<prop key="hibernate.hbm2ddl.auto">update</prop>
i don't want to update schema, and i want to display cnt attribute in JSP file through User Class(bean) when i using #Query annotation. how to accept a 'cnt' attribute using the User class without update schema?
'as' keyword usage not containing in detail(link)

It's difficult to understand what you need, but I'll try... ))
First create a projection:
public interface UserCount {
User getUser();
long getUserCount();
}
Then create a repository query method which return this projection:
public interface UserRepo extends JpaRepository<User, Long> {
#Query("select u as user, count(u) as userCount from User u group by u order by userCount desc")
List<UserCount> getUserCounts();
}
Then you can get list of UserCount projections and send it to your JSP view.
Additional reading:
Projections on repository query methods
Data transfer object
UPDATE
Controller:
#Controller
public class UserController {
#Autoware
private UserRepo repo;
#GetMapping("/")
public String withCount(Model model) {
List<UserCount> userCounts = getUserCounts();
model.addAttribute("userCounts", userCounts);
return "userCounts";
}
}
JSP
<table>
...
<c:forEach items="${userCounts}" var="item">
<tr>
<td>${item.user.group}</td>
...
<td>${item.userCount}</td>
</tr>
</c:forEach>
</table>
Some info: 1, 2
I recommend to switch from jsp to thymeleaf.

Related

Spring R2DBC query by example with projected/custom entity

The title maybe is not properly written but here is what, more or less, I want to achieve.
I would like to be able to write dynamic queries with use of Query by Example that would join multiple tables and create (projection?) DTO for me.
This DTO would have fields that are mapped to different columns in joined tables. Consider following:
Tables:
CREATE TABLE address
(
id SERIAL,
address_code VARCHAR(255) NOT NULL,
street_name VARCHAR(255),
building_number VARCHAR(255)
);
CREATE TABLE account
(
id SERIAL,
account_number BIGINT UNIQUE
);
CREATE TABLE customer
(
id SERIAL,
name VARCHAR(255)
)
I would like to be able to create a query which result would be:
address.address_code, account.account_number, customer.name
so basically the result would be a custom DTO. I also mentioned that I would like to have this backed up with Query by Example because I will to dynamically append WHERE clauses so I thought that if I created a DTO like:
public record CustomQueryResultDTO(String addressCode, BigInteger accountNumber, String name) {}
I could simply query just like it is in Spring R2DBC documentation.
The problem here is that I am not sure what should be a viable solution for such problem because on one hand I would like to reuse ReactiveQueryByExampleExecutor but that would mean that I have to create something like:
#Repository
public interface CustomQueryResultRepository extends ReactiveCrudRepository<CustomQueryResultDTO, Integer>, ReactiveQueryByExampleExecutor<CustomQueryResultDTO> {
}
Which kind of seems to me not a way to go as I do not have a corresponding table for CustomQueryResultDTO therefore there is really no mapping for this repository interface - or am I overthinking this and it is actually a way to go?
I think you are potentially overthinking it.
You can do it in a number of ways (note Java 17 text blocks):
Via R2DBC JPA-like #Query
Create a normal ReactiveCrudRepository but collect into a projection (DTOP)
// Repository
#Repository
public interface UserRefreshTokenRepository extends ReactiveCrudRepository<UserRefreshToken, Integer> {
#Query(
"""
select *
from user.user_refresh_tokens t
join user.user_infos c on c.user_id = t.user_id
where c.username = :username
"""
)
Flux<UserRefreshTokenDtop> findAllByUsername(String username);
}
// Entity
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
#ToString(exclude = {"refreshToken"})
#Table(schema = "user", name = "user_refresh_tokens")
public class UserRefreshToken {
#Id private Integer id;
private String userId;
private String username; # will be joined
private String ipAddr;
private OffsetDateTime createdAt;
private String refreshToken;
private OffsetDateTime refreshTokenIat;
private OffsetDateTime refreshTokenExp;
}
// DTO projection
public interface UserRefreshTokenDtop {
Integer getId();
String getUserId();
String getUsername(); # will be joined
String getIpAddr();
OffsetDateTime getRefreshTokenIat();
OffsetDateTime getRefreshTokenExp();
}
Via DatabaseClient
This one also uses TransactionalOperator to ensure query atomicity
private final DatabaseClient client;
private final TransactionalOperator operator;
#Override
public void deleteAllUsedExpiredAttempts(Duration resetInterval) {
// language=PostgreSQL
String allUsedExpiredAttempts = """
select t.id failed_id, c.id disable_id, t.username
from user.failed_sign_attempts t
join user.disable_sign_attempts c on c.username = t.username
where c.is_used = true
and :now >= c.expires_at + interval '%d seconds'
""";
// POTENTIAL SQL injection - half-arsed but %d ensures that only Number is allowed
client
.sql(String.format(allUsedExpiredAttempts, resetInterval.getSeconds()))
.bind("now", Instant.now())
.fetch()
.all()
.flatMap(this::deleteFailed)
.flatMap(this::deleteDisabled)
.as(operator::transactional)
.subscribe(v1 -> log.debug("Successfully reset {} user(s)", v1));
}
Via R2dbcEntityTemplate
I don't have a working example but it is pain in the ass to join via the .join() operator
If you are interested check the docs for R2dbcEntityTemplate
13.4.3. Fluent API > Methods for the Criteria Class
https://docs.spring.io/spring-data/r2dbc/docs/current/reference/html

SpringBoot JPA ManyToMany leftOuter join query and mapping

I have manyToMany relationship between Newsfeed and User in order to store favorite Newsfeeds of Uesrs.
Newsfeed
id
title
description
1
Test Title
Here is description
2
Title 2
Here is another description
Favorite_Newsfeed (ManyToMany Relationship)
newsfeed_id
user_id
1
1
2
2
1
2
Now I need to retrieve all the newsfeeds along with favorites filtered by user.
NewsfeedWithFavorite (Goal)
id
title
description
isFavorite
1
Test Title
Here is description
1
2
Title 2
Here is another description
I can do this by using case when in raw query but What is the best approach to retrieve data in this format using JPA? (I am new in JPA)
You have to use join in JPQL and then map favorites.
Repository :
public interface NewsFeedJpaRepository extends JpaRepository<NewsFeed, Long> {
#Query("SELECT nf , fnf.user.id FROM NewsFeed nf LEFT JOIN FavoriteNewsFeed fnf ON fnf.newsfeed = nf and fnf.user.id = :userId ")
Page<Object[]> findAllWithFavorite(Long userId, Pageable pageable);
}
create a dto like following:
public class NewsFeedWithFavorite extends NewsFeed {
private Boolean isFavorite;
public NewsFeedWithFavorite(){};
public NewsFeedWithFavorite(NewsFeed newsFeed, Long userID){
setId(newsFeed.getId());
setTitle(newsFeed.getTitle());
setDescription(newsFeed.getDescription());
if(userID !=null)
setFavorite( Boolean.TRUE);
else
setFavorite(Boolean.FALSE);
};
public Boolean getFavorite() {
return isFavorite;
}
public void setFavorite(Boolean favorite) {
isFavorite = favorite;
}
}
And finally in the Service :
public Page<NewsFeedWithFavorite> findAllWithFavorite(Long userId, Pageable pageable) {
Page<Object[]> result = newsFeedJpaRepository.findAllWithFavorite(userId,pageable);
return result.map(objArr -> new NewsFeedWithFavorite((NewsFeed) objArr[0], (Long) objArr[1]));
}
Do not use #ManyToMany annotation.
It will be extremely hard for you to add multiple columns and in general work with the auto generated Favorite_Newsfeed table.
The best way to handle this scenarior is to use a different table for User's news feed.
Check out the following structure.
public class User {
#OneToMany
private List<UserNewsFeed> newsFeeds;
}
public class NewsFeed {
public String description;
public String title;
}
public class UserNewsFeed {
#ManyToOne
private User user;
#ManyToOne
private NewsFeed newsFeed;
private boolean isFavorite;
}

How to test a jpa readonly query?

The model is simplified for the question.
I have this entity:
#Entity
public class Formation {
#Id
Long id;
String login;
String code;
String level;
// geters and Setters
With this repository:
public interface FormationRepository extends JpaRepository<Formation, Long> {
#Query(value = "SELECT UNIQUE l.id, l.login, d.code,d.level\n"
" FROM table_login l,\n" +
" table_diploma d,\n" +
" WHERE
" l.fhab_key = d.fhab_key\n" +
" AND l.login= :login", nativeQuery = true)
List<Formation> findAllByLogin(#Param("login")String login);
So far so good, this works.
Now I want to add test for the repository (with and h2database). But I can't save data, as the entity isn't mapped to a single table.
So this won't work:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class)
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class FormationRepositoryTest {
#Autowired
FormationRepository formationRepository;
#Test
public void communeRepositoryTest() {
Formation formation = new Formation();
formation.setId(123L);
formation.setDlog_login("123");
formationRepository.save(formation); // ok
formationRepository.findAllByLogin("123"); // ko -> Caused by: Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: scolarite.scol_droit_login is not mapped
}
}
Here I need to add, that I don't want sql files in my app.
So what would be a solution here ?
Change my model to create an entity by table ? (in real, my request use 8 inner joins, so it will be quite long to code all that...)
Another solution ?
If you do not want to create the entities for all your tables, you could pre-populate your testing H2 database by defining schema.sql and data.sql on your classpath.
schema.sql will contain the DDL statements to create all tables involved (you mention 8 tables).
data.sql will contain the insert statements needed to make your custom sql return login = 123.
You can find an example here.
Then your test code would be like:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class)
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class FormationRepositoryTest {
#Autowired
FormationRepository formationRepository;
#Test
public void communeRepositoryTest() {
List<Formation> formations = formationRepository.findAllByLogin("123");
Assert.assertEquals(formations.size(), 1);
}
}

Primefaces Datatable filterOption in columns

I have a database table displayed through Datatable (Primefaces 3.4.2) and I want to show a comboFilter in the header populated with values from the database table itself.
1) Since these values are not a PK or FK, I built a named query to retrieve distinct values for the prefDep column:
#NamedQuery(name = "Upb.findPrefDeps", query = "SELECT DISTINCT u FROM Upb u WHERE u.prefDep = :prefDep")
2) In my AbstractController:
public List<T> getPrefDepsList() {
if (prefDeps == null) {
prefDeps = this.ejbFacade.findPrefDeps();
}
return prefDeps;
}
3) As I inject the facade EJB, how can I build a managed bean property to be used in the filterOption below?
The managedBean:
#ManagedBean(name = "upbController")
#ViewScoped
public class UpbController extends AbstractController<Upb> implements Serializable {
#EJB
private UpbFacade ejbFacade;
public UpbController() {
super(Upb.class);
}
#PostConstruct
public void init() {
super.setFacade(ejbFacade);
}
public SelectItem[] getPrefDepOptions() {
return prefDepOptions; //build/populate this
}
}
The jsf:
<p:column filterBy="prefdep" headerText="PrefDep"
filterOptions="#{upbController.prefDepOptions}"
filterMatchMode="exact">
<h:outputText value="#{item.prefDep}" />
</p:column>
Thanks in advance.
I do not know if I understand your question the right way. You want to execute the named query once and store the distinct values in a property in the managed bean? For that you can use a PreRenderView event which would be called before rendering the page. You can call a init-function to load such values with this event.
You can also access the getter with the named query, but this may be called not only once.

Get eagerly collection of entities containing other eagerly got collections

I've got stuck on the M:N relation between entity and strings. An user can have more than one role and each role can be assigned to more than one user. Role is just a string. Roles are contained in table with two columns: roleId and roleName.
I've created two entities, but I'm absolutely unable to made it work. First entity is the user:
#Entity
#Table(name="appUsers")
public class UserEntity {
#Id
private String login;
private String password;
#OneToMany(fetch=FetchType.EAGER,mappedBy="user") //we always need to load user's roles
private Collection<UsersToRoles> roles;
#Transient
private Collection<String> roleNames;
public String getLogin() {
return login;
}
public String getPassword() {
return password;
}
#PostLoad
void prepareRoleNames() {
roleNames = new HashSet<String>(roles.size());
for (UsersToRoles mapping : roles)
roleNames.add(mapping.getNameOfRole());
}
public Collection<String> getRoles() {
return roleNames;
}
}
The second is entity associated with connecting table:
#Entity
#IdClass(UsersToRolesId.class)
public class UsersToRoles {
#Id
#SuppressWarnings("unused")
#Column(name="login")
private String login;
#Id
#SuppressWarnings("unused")
#Column(name="roleId")
private int roleId;
#ElementCollection(fetch=FetchType.EAGER)
#CollectionTable(name="userRoles", joinColumns={#JoinColumn(name="roleId")})
private List<String> roleName;
#ManyToOne
#JoinColumn(name="login")
#SuppressWarnings("unused")
private UserEntity user;
public String getNameOfRole() {
if (roleName.isEmpty())
throw new CommonError("Role name for roleId=" + roleId, AppErrors.ACCESSOR_UNAVAILABLE);
return roleName.get(0);
}
}
class UsersToRolesId {
private String login;
private int roleId;
/**
* Implicit constructor is not public. We have to
* declare public non-parametric constructor manually.
*/
public UsersToRolesId() {
}
#Override
public int hashCode() {
return 17*login.hashCode() + 37*roleId;
}
#Override
public boolean equals(Object obj) {
if (!(obj instanceof UsersToRolesId))
return false;
UsersToRolesId ref = (UsersToRolesId)obj;
return (this.login.equals(ref.login) && this.roleId == ref.roleId);
}
}
And the problem is, that the roleName collection is always null. I'm unable to get it work. When I make a mistake in table name in #CollectionTable annotation, it still works. The JPA does not fetch the subcollection at all. It makes select from table of user joined with table UsersToRoles, but the join to table userRoles is missing.
Can I ever do that? Can I get eagerly collection of entities containing another eagerly fetched collections?
Your mapping is completely wrong. UsersToRoles has a roleId column. Thus it refers to a single role. How could it have a collection of role names? The login column is mapped twice in the entity. Moreover, this looks like a simple join table to me, without any other attribute than the roleId and the login, which are foreign keys to the IDs of User and Role, respectively.
You should have two entities : User and Role, with a ManyToMany association using the UsersToRoles table as join table. That's it. The UsersToRoles table should not be mapped as an entity: it's a pure join table.
JPA providers usually have a configuration property denoting default eager fetch depth, i.e. hibernate.max_fetch_depth for Hibernate. Check if you can see more when you increase it.
Also, think about your design. Fetching subcollections of a collection eagerly might be a good idea only in limited scenarios (performance-wise). When you annotate your entity like that, you're going to use eager fetching in all use cases. Perhaps you'd be better off with "lazy" and fetching it eagerly only explicitly, with a query with a JOIN FETCH clause?