JPA passing list to IN clause in named native query

前端 未结 10 1073

I know I can pass a list to named query in JPA, but how about NamedNativeQuery? I have tried many ways but still can\'t just pass the list to a NamedNativeQuery. Anyone know

相关标签:
10条回答
  • 2020-11-30 07:44

    Tried in JPA2 with Hibernate as provider and it seems hibernate does support taking in a list for "IN" and it works. (At least for named queries and I believe it will be similar with named NATIVE queries) What hibernate does internally is generate dynamic parameters, inside the IN same as the number of elements in the passed in list.

    So in you example above

    List<Object[]> userList = em.createNamedQuery("User.findByUserIdList").setParameter("userIdList", list).getResultList();
    

    If list has 2 elements the query will look like

    select u.user_id, u.dob, u.name, u.sex, u.address from user u "+
             "where u.user_id in (?, ?)
    

    and if it has 3 elements it looks like

    select u.user_id, u.dob, u.name, u.sex, u.address from user u "+
             "where u.user_id in (?, ?, ?)
    
    0 讨论(0)
  • 2020-11-30 07:45

    can be as simple as:

    @Query(nativeQuery =true,value = "SELECT * FROM Employee as e WHERE e.employeeName IN (:names)")  
     List<Employee> findByEmployeeName(@Param("names") List<String> names);
    
    0 讨论(0)
  • 2020-11-30 07:48

    A list is not a valid parameter for a native SQL query, as it cannot be bound in JDBC. You need to have a parameter for each argument in the list.

    where u.user_id in (?id1, ?id2)

    This is supported through JPQL, but not SQL, so you could use JPQL instead of a native query.

    Some JPA providers may support this, so you may want to log a bug with your provider.

    0 讨论(0)
  • 2020-11-30 07:49

    In my case ( EclipseLink , PostGreSQL ) this works :

        ServerSession serverSession = this.entityManager.unwrap(ServerSession.class);
        Accessor accessor = serverSession.getAccessor();
        accessor.reestablishConnection(serverSession);
        BigDecimal result;
        try {
            Array jiraIssues = accessor.getConnection().createArrayOf("numeric", mandayWorkLogQueryModel.getJiraIssues().toArray());
            Query nativeQuery = this.entityManager.createNativeQuery(projectMandayWorkLogQueryProvider.provide(mandayWorkLogQueryModel));
            nativeQuery.setParameter(1,mandayWorkLogQueryModel.getPsymbol());
            nativeQuery.setParameter(2,jiraIssues);
            nativeQuery.setParameter(3,mandayWorkLogQueryModel.getFrom());
            nativeQuery.setParameter(4,mandayWorkLogQueryModel.getTo());
            result = (BigDecimal) nativeQuery.getSingleResult();
        } catch (Exception e) {
            throw new DataAccessException(e);
        }
    
        return result;
    

    Also in query cannot use IN(?) because you will get error like :

    Caused by: org.postgresql.util.PSQLException: ERROR: operator does not exist: numeric = numeric[]

    'IN(?)' must be swapped to '= ANY(?)'

    My solution was based on Erhannis concept.

    0 讨论(0)
  • 2020-11-30 07:50

    currently I use JPA 2.1 with Hibernate

    I also use IN condition with native query. Example of my query

    SELECT ... WHERE table_name.id IN (?1)
    

    I noticed that it's impossible to pass String like "id_1, id_2, id_3" because of limitations described by James

    But when you use jpa 2.1 + hibernate it's possible to pass List of string values. For my case next code is valid:

        List<String> idList = new ArrayList<>();
        idList.add("344710");
        idList.add("574477");
        idList.add("508290");
    
        query.setParameter(1, idList);
    
    0 讨论(0)
  • 2020-11-30 07:51

    It's not possible with standard JPA. Hibernate offers the proprietary method setParameterList(), but it only works with Hibernate sessions and is not available in JPA's EntityManager.

    I came up with the following workaround for Hibernate, which is not ideal but almost standard JPA code and has some nice properties to it.

    For starters you can keep the named native query nicely separated in a orm.xml file:

    <named-native-query name="Item.FIND_BY_COLORS" result-class="com.example.Item">
        <query>
            SELECT i.*
            FROM item i
            WHERE i.color IN ('blue',':colors')
            AND i.shape = :shape
        </query>
    </named-native-query>
    

    The placeholder is wrapped in single quotes, so it's a valid native JPA query. It runs without setting a parameter list and would still return correct results when other matching color parameters are set around it.

    Set the parameter list in your DAO or repository class:

    @SuppressWarnings("unchecked")
    public List<Item> findByColors(List<String> colors) {
        String sql = getQueryString(Item.FIND_BY_COLORS, Item.class);
        sql = setParameterList(sql, "colors", colors);
    
        return entityManager
                .createNativeQuery(sql, Item.class)
                .setParameter("shape", 'BOX')
                .getResultList();
    }
    

    No manual construction of query strings. You can set any other parameter as you normally would.

    Helper methods:

    String setParameterList(String sql, String name, Collection<String> values) {
        return sql.replaceFirst(":" + name, String.join("','", values));
    }
    
    String getQueryString(String queryName, Class<?> resultClass) {
        return entityManager
                .createNamedQuery(queryName, resultClass)
                .unwrap(org.hibernate.query.Query.class) // Provider specific
                .getQueryString();
    }
    

    So basically we're reading a query string from orm.xml, manually set a parameter list and then create the native JPA query. Unfortunately, createNativeQuery().getResultList() returns an untyped query and untyped list even though we passed a result class to it. Hence the @SuppressWarnings("unchecked").

    Downside: Unwrapping a query without executing it may be more complicated or impossible for JPA providers other than Hibernate. For example, the following might work for EclipseLink (untested, taken from Can I get the SQL string from a JPA query object?):

    Session session = em.unwrap(JpaEntityManager.class).getActiveSession();
    DatabaseQuery databaseQuery =     query.unwrap(EJBQueryImpl.class).getDatabaseQuery();
    databaseQuery.prepareCall(session, new DatabaseRecord());
    Record r = databaseQuery.getTranslationRow();
    String bound = databaseQuery.getTranslatedSQLString(session, r);
    String sqlString = databaseQuery.getSQLString();
    

    An alternative might be to store the query in a text file and add code to read it from there.

    0 讨论(0)
提交回复
热议问题