How to fetch record from db by ID? - java

I got this code, but I think this is bad approach, what if there are like 100k results, it will return 100k new People? Are there any other methods that I could use for e.g ResultSetExtractor but Im not quite sure how to implement that. Also, should I use try catch block?
public Optional<Person> getPerson(int id) {
String sql = "SELECT id, name FROM people WHERE id = ?";
List<Person> people = jdbcTemplate.query(sql, (result, index) -> {
return new Person(
result.getInt("id"),
result.getString("name")
);
}, id);
return people.stream().findFirst();
}

in a correct design the id field should be unique for every person so this code should only find 1 person as a result.
If the id field is not unique then it is not a good design but the code you wrote would be correct. You dont need try - catch because you a are using a list to store the results so even if theres more than 1 result it wont produce an exception. But that also doesnt make sense because in the end the method is only returning 1 person (people.stream.findFirst()).

If you change the query from
"SELECT id, name FROM people WHERE id = ?"
to
"SELECT id, name FROM people WHERE id = ? LIMIT 1"
it will only return the first person with a matching id.
However, id should ideally be unique for each person and the original query will return atmost one result in that case.

Related

result returns more than one elements

I am getting an error javax.persistence.NonUniqueResultException: result returns more than one elements
while getting the EmpID
Bypass oldBypass = bypassService.getByEmployeeId(employee.getId()); //Causing a problem- Method threw 'org.springframework.dao.IncorrectResultSizeDataAccessException' exception.
ByPassService.java
Bypass getByEmployeeId(Long id);
ByPassServiceImpl.java
public Bypass getByEmployeeId(Long id) {
return bypassRepository.findByEmployeeId(id);
}
#Query("select d from Bypass d where d.employee.id = ?1 and d.isDeleted = 0")
Bypass findByEmployeeId(Long id)
This is my select statement.
Should i use the ArrayLists maybe ???
Is your query returning only one entry? You could run it separately and check its results. Normally the employee id should be the Primary Key in the employee table and that would enforce uniqueness.
If you don't want the employee id to be unique among your records, then yes, you should expect a collection of entries.

Returning List<Object> to variables;

I am doing a room database
#Query("SELECT distinct UserID FROM USER WHERE CovidStat=='T'")
public List<USER> checkCovid();
#Query("SELECT DISTINCT PID,VisitDate FROM USER_PLACE,USER WHERE User.UserID=USER_PLACE.UID AND UID=(:UID)")
int selectCovidPlace(int UID);
I wanted checkCovid() to return UserID where CovidStat='T' and I wanted to put the ID into selectCovidPlace to identify which place that ID been to. The problem is that checkCovid would return me a list instead of 1 variable since its not only 1 person who would have CovidStat='T', but im not sure that how can I put a list into selectCovidPlace().
I'm not familiar with SQLite. But the SQL syntax should be generic.
How about use IN statement?
like this ... WHERE User.UserID=USER_PLACE.UID AND UID IN (:UID)")
And then you can pass List as parameter.
Here you should use IN clause as below:
#Query("SELECT DISTINCT PID,VisitDate FROM USER_PLACE,USER WHERE User.UserID=USER_PLACE.UID AND UID IN (:UIDs)")
int selectCovidPlace(List<Integer> UIDs);

How to know the missing items from Spring Data JPA's findAllById method in an efficient way?

Consider this code snippet below:
List<String> usersList = Arrays.asList("john", "jack", "jill", "xxxx", "yyyy");
List<User> userEntities = userRepo.findAllById(usersList);
User class is a simple Entity object annotated with #Entity and has an #Id field which is of String datatype.
Assume that in db I have rows corresponding to "john", "jack" and "jill". Even though I passed 5 items in usersList(along with "xxxx" and "yyyy"), findAllById method would only return 3 items/entities corresponding to "john","jack",and "jill".
Now after the call to findAllById method, what's the best, easy and efficient(better than O(n^2) perhaps) way to find out the missing items which findAllById method did not return?(In this case, it would be "xxxx" and "yyyy").
Using Java Sets
You could use a set as the source of filtering:
Set<String> usersSet = new HashSet<>(Arrays.asList("john", "jack", "jill", "xxxx", "yyyy"));
And now you could create a predicate to filter those not present:
Set<String> foundIds = userRepo.findAllById(usersSet)
.stream()
.map(User::getId)
.collect(Collectors.toSet());
I assume the filter should be O(n) to go over the entire results.
Or you could change your repository to return a set of users ideally using a form of distinct clause:
Set<String> foundIds = userRepo.findDistinctById(usersSet)
.stream()
.map(User::getId)
.collect(Collectors.toSet());;
And then you can just apply a set operator:
usersSet.removeAll(foundIds);
And now usersSet contains the users not found in your result.
And a set has a O(1) complexity to find an item. So, I assume this should be O(sizeOf(userSet)) to remove them all.
Alternatively, you could iterate over the foundIds and gradually remove items from the userSet. Then you could short-circuit the loop algorithm in the event you realize that there are no more userSet items to remove (i.e. the set is empty).
Filtering Directly from Database
Now to avoid all this, you can probably define a native query and run it in your JPA repository to retrieve only users from your list which didn't exist in the database. The query would be somewhat as follows that I did in PostgreSQL:
WITH my_users AS(
SELECT 'john' AS id UNION SELECT 'jack' UNION SELECT 'jill'
)
SELECT id FROM my_users mu
WHERE NOT EXISTS(SELECT 1 FROM users u WHERE u.id = mu.id);
Spring Data: JDBC Example
Since the query is dynamic (i.e. the filtering set could be of different sizes every time), we need to build the query dynamically. And I don't believe JPA has a way to do this, but a native query might do the trick.
You could either pack a JdbcTemplate query directly into your repository or use JPA native queries manually.
#Repository
public class UserRepository {
private final JdbcTemplate jdbcTemplate;
public UserRepository(JdbcTemplate jdbcTemplate) {this.jdbcTemplate = jdbcTemplate;}
public Set<String> getUserIdNotFound(Set<String> userIds) {
StringBuilder sql = new StringBuilder();
for(String userId : userIds) {
if(sql.length() > 0) {
sql.append(" UNION ");
}
sql.append("SELECT ? AS id");
}
String query = String.format("WITH my_users AS (%sql)", sql) +
"SELECT id FROM my_users mu WHERE NOT EXISTS(SELECT 1 FROM users u WHERE u.id = mu.id)";
List<String> myUsers = jdbcTemplate.queryForList(query, userIds.toArray(), String.class);
return new HashSet<>(myUsers);
}
}
Then we just do:
Set<String> usersIds = Set.of("john", "jack", "jill", "xxxx", "yyyy");
Set<String> notFoundIds = userRepo.getUserIdNotFound(usersIds);
There is probably a way to do it with JPA native queries. Let me see if I can do one of those and put it in the answer later on.
You can write your own algorithm that finds missing users. For example:
List<String> missing = new ArrayList<>(usersList);
for (User user : userEntities){
String userId = user.getId();
missing.remove(userId);
}
In the result you will have a list of user-ids that are missing:
"xxxx" and "yyyy"
You can just add a method to your repo:
findByIdNotIn(Collection<String> ids) and Spring will make the query:
See here:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods
Note (from the docs):
In and NotIn also take any subclass of Collection as aparameter as well as arrays or varargs.

JDBC Template - One-To-Many

I have a class that looks like this. I need to populate it from two database tables, which are also shown below. Is there any preferred way to do this?
My thought is to have a service class to select a List<> via a ResultSetExtractor from a DAO. Then do a foreach on that list, and select a List<> of emails for the individual person via another ResultSetExtractor, and attach it from with the foreach loop.
Is there a better way, or is this as good as it gets?
public class Person {
private String personId;
private String Name;
private ArrayList<String> emails;
}
create table Person (
person_id varchar2(10),
name varchar2(30)
);
create table email (
person_id varchar2(10),
email varchar2(30)
);
This is best solved by an ORM. With JDBC, you have to do by hand what an ORM would do for you. Executing N + 1 queries is very inefficient. You should execute a single query, and build your objects manually. Cumbersome, but not hard:
select person.id, person.name, email.email from person person
left join email on person.id = email.person_id
...
Map<Long, Person> personsById = new HashMap<>();
while (rs.next()) {
Long id = rs.getLong("id");
String name = rs.getString("name");
String email = rs.getString("email");
Person person = personsById.get(id);
if (person == null) {
person = new Person(id, name);
personsById.put(person.getId(), person);
}
person.addEmail(email);
}
Collection<Person> persons = personsById.values();
I was looking for something similar, and although the answer is perfectly valid I went with this nice library instead https://simpleflatmapper.org/0203-joins.html
It also integrates perfectly with Spring boot.
main advantage is that you have a clean repository layer, it uses your pojo and makes refactoring much easier, and like hibernate you can still map deep nested and complex one to many and still be in control of what is executed.
It also has a nice jdbctemplate CRUD and Java 13 finally brings support for multi-line string literals which is very good for sql statements readability. hope this helps someone :)
In my case, I had to use the LinkedHashMap to keep the query result ordered by the position field.
From JavaDoc:
LinkedHashMap: "This linked list defines the iteration ordering, which is normally the order in which keys were inserted into the map (insertion-order). Note that insertion order is not affected if a key is re-inserted into the map."
HashMap: "This class makes no guarantees as to the order of the map; in particular, it does not guarantee that the order will remain constant over time".
TIP: using the getOrDefault method eliminates the extra check for nullable object.
public List<BucketDto> findAll() {
var sql = """
SELECT
b.uuid bucket_uuid, b.position bucket_position, b.name bucket_name,
c.uuid card_uuid, c.position card_position, c.name card_name
FROM bucket AS b
LEFT JOIN card AS c ON c.bucket_id = b.id
ORDER BY b.position ASC, c.position ASC
""";
return jdbcTemplate.query(sql, rs -> {
Map<Double, BucketDto> resultMap = new LinkedHashMap<>();
while (rs.next()) {
var position = rs.getDouble("bucket_position");
var bucketDto = resultMap.getOrDefault(position, new BucketDto(
UUID.fromString(rs.getString("bucket_uuid")),
position,
rs.getString("bucket_name")));
if (Optional.ofNullable(rs.getString("card_uuid")).isPresent()) {
bucketDto.addCard(new CardDto(
UUID.fromString(rs.getString("card_uuid")),
rs.getDouble("card_position"),
rs.getString("card_name")));
}
resultMap.put(position, bucketDto);
}
return new ArrayList<>(resultMap.values());
});
}

How to avoid hibernate default order by id?

I make this query:
String query = FROM Account acc WHERE acc.id = ? OR acc.id = ? or acc.id = ?...
I have array of ids:
long[] accountIds= {327913,327652,327910,330511,330643};
Then I make
getHibernateTemplate().find(query, accountIds);
I see that the list of accounts I get back from this query is:
327652,327910,327913,330511,330643, obviously , ordered by id.
Any chance I get it back in the order I wrote the ids?
Will appreciate all the help
You may want to use Criteria and its addOrder.
Something like this:
DetachedCriteria cr = DetachedCriteria.forClass(entityClass);
//Add expressions or restrictions to your citeria
//And add your ordering
cr.addOrder(Order.asc("yourID"));
List<T> ls = getHibernateTemplate().findByCriteria(cr);
return ls;
You can't do it on query level.
You can sort after loading from db, something like this
long[] accountIds= {327913,327652,327910,330511,330643};
List<Account> afterHql = getHibernateTemplate().find(query, accountIds);
List<Account> sortedList = new ArrayList<Acount>();
for (long id : accountIds)
{
for (Account account : afterHql)
{
if (account.getId() == id)
{
sortedList.add(account);
}
}
}
It is not possible to fetch results by providing any entity in OR Query or Restrictions.in(). As by deafult when you fire this kind of query it will search for the results id wise. So it will give you results id wise only. You can change the order by using Criteria either in asc or desc. And if you want to have results as per you enter id, then second answer is the only option.
You can only order by column values returned by the query, in a sequence defined by the data type . Wouldn't it be better to pre-order the IDs you supply, and order the query result by ID, so they come out in the same order?

Categories