Отображение результатов NativeQuery в POJO
Я пытаюсь сопоставить результаты собственного запроса с POJO, используя @SqlResultSetMapping с @ConstructorResult. Вот мой код:
@SqlResultSetMapping(name="foo",
classes = {
@ConstructorResult(
targetClass = Bar.class,
columns = {
@ColumnResult(name = "barId", type = Long.class),
@ColumnResult(name = "barName", type = String.class),
@ColumnResult(name = "barTotal", type = Long.class)
})
})
public class Bar {
private Long barId;
private String barName;
private Long barTotal;
...
И затем в моем DAO:
Query query = em.createNativeQueryBar(QUERY, "foo");
... set some parameters ...
List<Bar> list = (List<Bar>) query.getResultList();
Я читал, что эта функция поддерживается только в JPA 2.1, но это то, что я использую. Здесь моя зависимость:
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.1-api</artifactId>
<version>1.0.0.Final</version>
</dependency>
Я нашел несколько ресурсов, включая этот: @ConstructorResult mapping в jpa 2.1. Но мне все еще не повезло.
Что мне не хватает? Почему невозможно найти SqlResultSetMapping?
javax.persistence.PersistenceException: org.hibernate.MappingException: Unknown SqlResultSetMapping [foo]
Ответы
Ответ 1
@SqlResultSetMapping
аннотация не должна помещаться в POJO. Поместите его в (любой) @Entity
класс. "Неизвестный SqlResultSetMapping [foo]" сообщает вам, что поставщик JPA не видит сопоставления под именем "foo". Пожалуйста, см. Другой мой ответ для правильного примера.
Ответ 2
Краткий рабочий пример:
DTO POJO класс
@lombok.Getter
@lombok.AllArgsConstructor
public class StatementDto {
private String authorName;
private Date createTime;
}
Боб репозитория:
@Repository
public class StatementNativeRepository {
@PersistenceContext private EntityManager em;
static final String STATEMENT_SQLMAP = "Statement-SQL-Mapping";
public List<StatementDto> findPipelinedStatements() {
Query query = em.createNativeQuery(
"select author_name, create_time from TABLE(SomePipelinedFun('xxx'))",
STATEMENT_SQLMAP);
return query.getResultList();
}
@SqlResultSetMapping(name= STATEMENT_SQLMAP, classes = {
@ConstructorResult(targetClass = StatementDto.class,
columns = {
@ColumnResult(name="author_name",type = String.class),
@ColumnResult(name="create_time",type = Date.class)
}
)
}) @Entity class SQLMappingCfgEntity{@Id int id;} // <- workaround
}
Ответ 3
Я могу сделать это следующим образом:
Session session = em().unwrap(Session.class);
SQLQuery q = session.createSQLQuery("YOUR SQL HERE");
q.setResultTransformer( Transformers.aliasToBean( MyNotMappedPojoClassHere.class) );
List<MyNotMappedPojoClassHere> postList = q.list();
Ответ 4
@Entity
@SqlResultSetMapping(name="ConnexionQueryBean",
entities={
@EntityResult(entityClass=com.collecteJ.business.bean.ConnexionQueryBean.class, fields={
@FieldResult(name="utilisateurId", column="UTILISATEUR_ID"),
@FieldResult(name="nom", column="NOM"),
@FieldResult(name="prenom", column="PRENOM"),
@FieldResult(name="nomConnexion", column="NOM_CONNEXION"),
@FieldResult(name="codeAgence", column="CODE_AGENCE"),
@FieldResult(name="codeBanque", column="CODE_BANQUE"),
@FieldResult(name="codeDevise", column="CODE_DEVISE"),
@FieldResult(name="codeCollecteur", column="CODE_COLLECTEUR")})
})
public class ConnexionQueryBean implements Serializable {
@Id
private long utilisateurId;
private String codeCollecteur;
private String nom;
private String prenom;
private String nomConnexion;
private String codeAgence;
private String codeBanque;
private String codeDevise;
public ConnexionQueryBean() {
}
public long getUtilisateurId() {
return utilisateurId;
}
public void setUtilisateurId(long utilisateurId) {
this.utilisateurId = utilisateurId;
}
public String getCodeCollecteur() {
return codeCollecteur;
}
public void setCodeCollecteur(String codeCollecteur) {
this.codeCollecteur = codeCollecteur;
}
public String getNom() {
return nom;
}
public void setNom(String nom) {
this.nom = nom;
}
public String getPrenom() {
return prenom;
}
public void setPrenom(String prenom) {
this.prenom = prenom;
}
public String getNomConnexion() {
return nomConnexion;
}
public void setNomConnexion(String nomConnexion) {
this.nomConnexion = nomConnexion;
}
public String getCodeAgence() {
return codeAgence;
}
public void setCodeAgence(String codeAgence) {
this.codeAgence = codeAgence;
}
public String getCodeBanque() {
return codeBanque;
}
public void setCodeBanque(String codeBanque) {
this.codeBanque = codeBanque;
}
public String getCodeDevise() {
return codeDevise;
}
public void setCodeDevise(String codeDevise) {
this.codeDevise = codeDevise;
}
@Override
public String toString() {
return "ConnexionQueryBean{" + "utilisateurId=" + utilisateurId + ", codeCollecteur=" + codeCollecteur + ", nom=" + nom + ", prenom=" + prenom + ", nomConnexion=" + nomConnexion + ", codeAgence=" + codeAgence + ", codeBanque=" + codeBanque + ", codeDevise=" + codeDevise + '}';
}
На самом деле это не объект, поскольку он не соответствует таблице базы данных. Но аннотации @Entity
и @Id
являются обязательными для JPA для понимания отображения. Если вы действительно не хотите иметь @Entity / @Id
в этом классе, вы можете удалить аннотацию @SqlResultSetMapping
и поместить ее в любой другой объект, поскольку JPA может сканировать его.
Вы также должны убедиться, что ваш @ComponentScan
контент соответствует соответствующему пакету, если вы используете конфигурацию spring на основе java, вы должны явно объявить свою сущность в persistence.xml/orm.xml
в каталоге META-INF
.
Это вызов
String connexionQuery = "SELECT u.UTILISATEUR_ID, u.NOM, u.PRENOM, u.NOM_CONNEXION, a.CODE_AGENCE, a.CODE_BANQUE, a.CODE_DEVISE, c.CODE_COLLECTEUR FROM UTILISATEUR u, AGENCE a, COLLECTEUR c "
+ " WHERE (a.CODE_AGENCE = c.CODE_AGENCE AND u.UTILISATEUR_ID = c.UTILISATEUR_ID AND u.NOM_CONNEXION = '"+nomConnextion+"')";
ConnexionQueryBean ConnexionResults = (ConnexionQueryBean) defaultService.getEntityManager().createNativeQuery(connexionQuery,"ConnexionQueryBean").getSingleResult();
System.out.println(ConnexionResults.toString());
Я использую Spring, JPA 2.1, Hibernate 5 и Oracle, я думаю, что это может оказаться невозможным с более низкой версией JPA, найти более http://www.thoughts-on-java.org/result-set-mapping-complex-mappings/
Ответ 5
QLRM может быть альтернативой: http://simasch.github.io/qlrm/
Он не связан с конкретной реализацией JPA, а также работает с JDBC.
Ответ 6
У меня есть немного различный ответ, который только что получен из ответа с помощью wildloop.
Вот мой ответ:
Класс констант: Constants.java
public class Constants {
public final String TESTQUERYRESULT_MAPPING_NAME = "TestQueryResultMapping";
}
Класс отображения результатов: TestQueryResult.java
import lombok.Getter;
import lombok.Setter;
import javax.persistence.Entity;
import javax.persistence.EntityResult;
import javax.persistence.FieldResult;
import javax.persistence.Id;
import javax.persistence.SqlResultSetMapping;
@Getter
@Setter
@SqlResultSetMapping(
//name = "TestQueryResultMapping"
name = Constants.TESTQUERYRESULT_MAPPING_NAME
,entities = @EntityResult(
entityClass = TestQueryResult.class
,fields = {
@FieldResult(name = "rowId", column = "row_id")
,@FieldResult(name = "rowName", column = "row_name")
,@FieldResult(name = "value", column = "row_value")
}
)
)
@Entity
public class TestQueryResult {
@Id
private Integer rowId;
private String rowName;
private String value;
}
Тогда... где-то в моем коде реализации репозитория:
public class TestQueryRepository {
//... some code here to get the entity manager here
public TestQueryResult getTopMost(Integer rowName) {
//... some code here
String queryString = "... some query string here" + rowName;
TestQueryResult testQueryResult = null;
//this.entityManager.createNativeQuery(queryString ,"TestQueryResultMapping").getResultList();
List<TestQueryResult> results = this.entityManager.createNativeQuery(queryString ,Constants.TESTQUERYRESULT_MAPPING_NAME).getResultList();
if (results != null && !results.isEmpty()) {
testQueryResult = results.get(0);
}
return testQueryResult;
}
}
... тогда виола! Я получил некоторые результаты: D!
Cheers,
Артанис Зератул
Ответ 7
Проблема с добавлением @Entity
в ваш DTO POJO заключается в том, что он создаст таблицу в вашей БД, которая вам не нужна. Необходимость добавить @Id
и временное ключевое слово в необходимые поля также является проблемой. Простое решение - переместить ваш @SqlResultSetMapping
в абстрактный класс.
@MappedSuperclass
@SqlResultSetMapping(name="foo",
classes = {
@ConstructorResult(
targetClass = Bar.class,
columns = {
@ColumnResult(name = "barId", type = Long.class),
@ColumnResult(name = "barName", type = String.class),
@ColumnResult(name = "barTotal", type = Long.class)
})
})
public abstract class sqlMappingCode {}
Не забудьте добавить @MappedSuperclass
. Это обеспечит автоматическую привязку Hibernate к вашему отображению.
Ответ 8
Это решение не зависит от реализации JPA. Как только вы соберете результат нативного запроса как
List<Object[]> = em.createNativeQueryBar(QUERY, "foo").getResultList();
и если вам нужно сопоставить каждый элемент списка с личностью, например
Class Person { String name; Int age; }
где элемент списка [0] - это имя, а элемент [1] - это возраст.
Вы можете конвертировать Object [] в Person, используя ObjectMapper. Вам необходимо добавить аннотацию @JsonFormat для класса.
@JsonFormat(shape = JsonFormat.Shape.ARRAY)
Class Person { String name; Int age; }
и преобразовать Object [] в Person как
ObjectMapper mapper = new ObjectMapper();
JSONArray jsonArray = new JSONArray(Arrays.asList(attributes)).toString();
Person person = mapper.readValue(jsonArray, Person.class);