1

Say for example I have an sql table

create table CUSTOMER
(
  first_name      VARCHAR2(30) not null,
  last_name       VARCHAR2(30) not null,
  age             VARCHAR2(30) not null,
  credit_card_num VARCHAR2(30) not null UNIQUE
);

and I want to get a Java Map where the map keys are values of CUSTOMER.credit_card_num and the map values are all the properties of CUSTOMER so for example:

1234134cardnumber -> Customer(first_name: Jack, age: 23, credit_card_num: 1234134cardnumber),
123faef -> Customer(first_name: Mike, age: 43, credit_card_num: 123faef)

etc...

I'll accept any way to do it in JPA or Native Query

Update

as kindly referred by https://stackoverflow.com/users/1025118/vlad-mihalcea it can be done by https://vladmihalcea.com/why-you-should-use-the-hibernate-resulttransformer-to-customize-result-set-mappings/

Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
Nemanja Žunić
  • 181
  • 2
  • 8
  • See [this](https://stackoverflow.com/questions/7876724/how-to-return-mapkey-value-with-hql) – SternK Dec 18 '19 at 08:36
  • @SternK so it's not possible to get `Map` or `Map>` without a `for` loop. The best thing we can get is `List>` – Nemanja Žunić Dec 18 '19 at 09:15
  • 1
    You can obtain query results only via `Query.getResultList()` that returns `List` (JPA 1.0) or `TypedQuery.getResultList()` that returns `List` (JPA 2.0) or 'TypedQuery.getResultStream()' that returns `Stream` (JPA 2.2). So, there are no way for obtaining query results as `Map`. – SternK Dec 18 '19 at 10:51

2 Answers2

3

There are two solutions to this problem.

Assuming you are using the following Post entity:

@Entity(name = "Post")
@Table(name = "post")
public class Post {

    @Id
    private Long id;

    private String title;

    @Column(name = "created_on")
    private LocalDate createdOn;

    public Long getId() {
        return id;
    }

    public Post setId(Long id) {
        this.id = id;
        return this;
    }

    public String getTitle() {
        return title;
    }

    public Post setTitle(String title) {
        this.title = title;
        return this;
    }

    public LocalDate getCreatedOn() {
        return createdOn;
    }

    public Post setCreatedOn(LocalDate createdOn) {
        this.createdOn = createdOn;
        return this;
    }
}

And you have the following entities persisted in your database:

entityManager.persist(
    new Post()
        .setId(1L)
        .setTitle("High-Performance Java Persistence eBook has been released!")
        .setCreatedOn(LocalDate.of(2016, 8, 30))
);

entityManager.persist(
    new Post()
        .setId(2L)
        .setTitle("High-Performance Java Persistence paperback has been released!")
        .setCreatedOn(LocalDate.of(2016, 10, 12))
);

entityManager.persist(
    new Post()
        .setId(3L)
        .setTitle("High-Performance Java Persistence Mach 1 video course has been released!")
        .setCreatedOn(LocalDate.of(2018, 1, 30))
);

entityManager.persist(
    new Post()
        .setId(4L)
        .setTitle("High-Performance Java Persistence Mach 2 video course has been released!")
        .setCreatedOn(LocalDate.of(2018, 5, 8))
);

entityManager.persist(
    new Post()
        .setId(5L)
        .setTitle("Hypersistence Optimizer has been released!")
        .setCreatedOn(LocalDate.of(2019, 3, 19))
);

Using a Java 8 Stream

Here's how you can return a Map as requested by your question:

Map<Long, Post> postByIdMap = entityManager
.createQuery(
    "select p " +
    "from Post p ", Post.class)
.getResultStream()
.collect(
    Collectors.toMap(
        Post::getId,
        Function.identity()
    )
);

assertEquals(
    "High-Performance Java Persistence eBook has been released!",
    postByIdMap.get(1L).getTitle()
);

assertEquals(
    "Hypersistence Optimizer has been released!",
    postByIdMap.get(5L).getTitle()
);

Using a Hibernate ResultTransformer

The same goal can be achieved using a ResultTransformer:

Map<Long, Post> postByIdMap = (Map<Long, Post>) entityManager
.createQuery(
    "select p " +
    "from Post p ")
.unwrap(org.hibernate.query.Query.class)
.setResultTransformer(
    new ResultTransformer() {

        Map<Long, Post> result = new HashMap<>();

        @Override
        public Object transformTuple(Object[] tuple, String[] aliases) {
            Post post = (Post) tuple[0];
            result.put(
                post.getId(),
                post
            );
            return tuple;
        }

        @Override
        public List transformList(List collection) {
            return Collections.singletonList(result);
        }
    }
)
.getSingleResult();

assertEquals(
    "High-Performance Java Persistence eBook has been released!",
    postByIdMap.get(1L).getTitle()
);

assertEquals(
    "Hypersistence Optimizer has been released!",
    postByIdMap.get(5L).getTitle()
);
Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
1

The best way i have seen is with a Spring repository

public interface CustomerRepository extends CrudRepository<Customer, Long> {

    List<Customer> findAll();

    default Map<Long,Customer> findMapById() {
        return findAll().stream().collect(Collectors.toMap(Customer::getId,
                Function.identity()));
    }

    default Map<String,Customer> findMapByCreditcard() {
        return findAll().stream().collect(Collectors.toMap(Customer::getCreditCardNum,
                Function.identity()));
    }
}

But it is not JPA itself returning the map

FredvN
  • 504
  • 1
  • 3
  • 14
  • I would like to avoid converting List to Map from Java code. I think that from a single select result `Customer(first_name: Jack, age: 23, credit_card_num: 1234134cardnumber)` we have enough info to make it a map and query should be able to do it – Nemanja Žunić Dec 18 '19 at 10:44
  • 1
    The JPA framework will not return a map as mentioned by @SternK. But you can fall back to plain JDBC and process your resultset by yourself and create a map entry for each record of the resultset. – FredvN Dec 18 '19 at 12:13