[SPRING] JDBCTemplate은 BeanPropertyRowMapper를 사용하여 중첩 POJO를 설정합니다.
SPRINGJDBCTemplate은 BeanPropertyRowMapper를 사용하여 중첩 POJO를 설정합니다.
주어진 POJO의 예제는 다음과 같습니다 (모든 속성에 대해 Getters 및 Setter 가정)
class User {
String user_name;
String display_name;
}
class Message {
String title;
String question;
User user;
}
DB 필드가 POJO의 속성과 일치하는 BeanPropertyRowMapper를 사용하여 데이터베이스 (내 경우에는 포스트 그레스)를 쉽게 쿼리하고 Message 클래스 목록을 채울 수 있습니다 (DB 테이블에 POJO 속성에 해당하는 필드가 있다고 가정).
NamedParameterDatbase.query("SELECT * FROM message", new BeanPropertyRowMapper(Message.class));
나도 궁금하네요. 단일 쿼리를 만들고 / 또는 메시지 내에서 '사용자'POJO의 속성을 채울 수있는 행 매퍼를 만드는 편리한 방법이 있습니까?
즉, 쿼리의 각 결과 행이있는 일부 통합 마법 :
SELECT * FROM message, user WHERE user_id = message_id
연결된 사용자가 채워진 메시지 목록을 생성합니다.
사용 사례 :
궁극적으로, 클래스는 스프링 컨트롤러에서 직렬화 된 객체로 전달되며 클래스는 중첩되어 결과 JSON / XML이 적절한 구조를 갖습니다.
이 상황은 두 개의 쿼리를 실행하고 루프의 각 메시지에 대한 사용자 속성을 수동으로 설정하여 해결됩니다. 유용하지만 더 우아한 방법이 가능해야한다고 생각합니다.
업데이트 : 사용 된 솔루션 -
커스텀 행 매퍼 (custom mapper)를 사용하여 답변을 얻으려는 영감을 얻으려는 @ Will Keeling의 명언 - 내 솔루션은 필드 할당을 자동화하기 위해 빈 속성 맵을 추가합니다.
주의해야 할 점은 관련 테이블 이름에 접두어가 붙을 수 있도록 쿼리를 구조화하는 것입니다 (단, 이렇게하는 표준 규칙이 없으므로 쿼리가 프로그램 적으로 작성됩니다).
SELECT title AS "message.title", question AS "message.question", user_name AS "user.user_name", display_name AS "user.display_name" FROM message, user WHERE user_id = message_id
그런 다음 사용자 정의 행 맵퍼는 여러 bean 맵을 작성하고 컬럼 접 두부에 따라 해당 특성을 설정합니다 (C 럼 이름을 얻기 위해 메타 데이터 사용).
public Object mapRow(ResultSet rs, int i) throws SQLException {
HashMap<String, BeanMap> beans_by_name = new HashMap();
beans_by_name.put("message", BeanMap.create(new Message()));
beans_by_name.put("user", BeanMap.create(new User()));
ResultSetMetaData resultSetMetaData = rs.getMetaData();
for (int colnum = 1; colnum <= resultSetMetaData.getColumnCount(); colnum++) {
String table = resultSetMetaData.getColumnName(colnum).split("\\.")[0];
String field = resultSetMetaData.getColumnName(colnum).split("\\.")[1];
BeanMap beanMap = beans_by_name.get(table);
if (rs.getObject(colnum) != null) {
beanMap.put(field, rs.getObject(colnum));
}
}
Message m = (Task)beans_by_name.get("message").getBean();
m.setUser((User)beans_by_name.get("user").getBean());
return m;
}
다시 말하지만, 이것은 2 개의 클래스 조인에 대해 과도한 것처럼 보일 수 있지만 IRL 사용 사례에는 수십 개의 필드가있는 여러 테이블이 포함됩니다.
해결법
-
==============================
1.아마도 메시지 및 사용자간에 집계 조인 쿼리의 각 행을 메시지 및 중첩 된 사용자에 매핑 할 수있는 사용자 정의 RowMapper를 전달할 수 있습니다. 이 같은:
아마도 메시지 및 사용자간에 집계 조인 쿼리의 각 행을 메시지 및 중첩 된 사용자에 매핑 할 수있는 사용자 정의 RowMapper를 전달할 수 있습니다. 이 같은:
List<Message> messages = jdbcTemplate.query("SELECT * FROM message m, user u WHERE u.message_id = m.message_id", new RowMapper<Message>() { @Override public Message mapRow(ResultSet rs, int rowNum) throws SQLException { Message message = new Message(); message.setTitle(rs.getString(1)); message.setQuestion(rs.getString(2)); User user = new User(); user.setUserName(rs.getString(3)); user.setDisplayName(rs.getString(4)); message.setUser(user); return message; } });
-
==============================
2.Spring은 새로운 AutoGrowNestedPaths 프로퍼티를 BeanMapper 인터페이스에 도입했다.
Spring은 새로운 AutoGrowNestedPaths 프로퍼티를 BeanMapper 인터페이스에 도입했다.
SQL 쿼리가 열 이름의 형식을 지정하는 한. 구분 기호 (이전과 같이)이면 행 매퍼는 자동으로 내부 객체를 대상으로 지정합니다.
이를 통해 다음과 같이 새로운 일반 행 매퍼를 만들었습니다.
질문:
SELECT title AS "message.title", question AS "message.question", user_name AS "user.user_name", display_name AS "user.display_name" FROM message, user WHERE user_id = message_id
행 맵핑 :
package nested_row_mapper; import org.springframework.beans.*; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.support.JdbcUtils; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; public class NestedRowMapper<T> implements RowMapper<T> { private Class<T> mappedClass; public NestedRowMapper(Class<T> mappedClass) { this.mappedClass = mappedClass; } @Override public T mapRow(ResultSet rs, int rowNum) throws SQLException { T mappedObject = BeanUtils.instantiate(this.mappedClass); BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(mappedObject); bw.setAutoGrowNestedPaths(true); ResultSetMetaData meta_data = rs.getMetaData(); int columnCount = meta_data.getColumnCount(); for (int index = 1; index <= columnCount; index++) { try { String column = JdbcUtils.lookupColumnName(meta_data, index); Object value = JdbcUtils.getResultSetValue(rs, index, Class.forName(meta_data.getColumnClassName(index))); bw.setPropertyValue(column, value); } catch (TypeMismatchException | NotWritablePropertyException | ClassNotFoundException e) { // Ignore } } return mappedObject; } }
-
==============================
3.조금 늦은 파티이지만 같은 질문을 할 때 이것을 찾았고 앞으로 다른 사람들에게 유리할 수있는 다른 해결책을 찾았습니다.
조금 늦은 파티이지만 같은 질문을 할 때 이것을 찾았고 앞으로 다른 사람들에게 유리할 수있는 다른 해결책을 찾았습니다.
불행히도 고객 RowMapper를 만들지 않고도 중첩 된 시나리오를 구현할 수있는 기본 방법은 없습니다. 그러나 나는 여기서 다른 솔루션보다 더 쉽게 사용자 정의 RowMapper를 만드는 방법을 공유 할 것이다.
주어진 시나리오를 통해 다음을 수행 할 수 있습니다.
class User { String user_name; String display_name; } class Message { String title; String question; User user; } public class MessageRowMapper implements RowMapper<Message> { @Override public Message mapRow(ResultSet rs, int rowNum) throws SQLException { User user = (new BeanPropertyRowMapper<>(User.class)).mapRow(rs,rowNum); Message message = (new BeanPropertyRowMapper<>(Message.class)).mapRow(rs,rowNum); message.setUser(user); return message; } }
BeanPropertyRowMapper로 기억해야 할 핵심 사항은 다음과 같은 예외를 제외하고는 열의 이름과 클래스 멤버의 속성을 문자에 따라야한다는 것입니다 (Spring 설명서 참조).
-
==============================
4.업데이트 : 2014 년 10 월 10 일 나는 더 이상이 rowmapping 중 하나를 수행하지 않는다. 어노테이션을 통해 선택적 JSON 표현을 훨씬 우아하게 처리 할 수 있습니다. 이 요지를보십시오.
업데이트 : 2014 년 10 월 10 일 나는 더 이상이 rowmapping 중 하나를 수행하지 않는다. 어노테이션을 통해 선택적 JSON 표현을 훨씬 우아하게 처리 할 수 있습니다. 이 요지를보십시오.
나는 하루 종일 더 나은 부분을 3 층 중첩 된 객체의 경우에 대해 파악하려고 노력하고 마침내 그것을 못 박았습니다. 여기에 내 상황이있다.
계정 (즉, 사용자) - 1tomany -> 역할 - 1tomany ->보기 (사용자는 볼 수 있음)
(이 POJO 클래스는 맨 아래에 붙여 넣습니다.)
컨트롤러가 다음과 같은 객체를 반환하기를 원했습니다.
[ { "id" : 3, "email" : "catchall@sdcl.org", "password" : "sdclpass", "org" : "Super-duper Candy Lab", "role" : { "id" : 2, "name" : "ADMIN", "views" : [ "viewPublicReports", "viewAllOrders", "viewProducts", "orderProducts", "viewOfferings", "viewMyData", "viewAllData", "home", "viewMyOrders", "manageUsers" ] } }, { "id" : 5, "email" : "catchall@stereolab.com", "password" : "stereopass", "org" : "Stereolab", "role" : { "id" : 1, "name" : "USER", "views" : [ "viewPublicReports", "viewProducts", "orderProducts", "viewOfferings", "viewMyData", "home", "viewMyOrders" ] } }, { "id" : 6, "email" : "catchall@ukmedschool.com", "password" : "ukmedpass", "org" : "University of Kentucky College of Medicine", "role" : { "id" : 2, "name" : "ADMIN", "views" : [ "viewPublicReports", "viewAllOrders", "viewProducts", "orderProducts", "viewOfferings", "viewMyData", "viewAllData", "home", "viewMyOrders", "manageUsers" ] } } ]
핵심은 스프링이 자동으로이 모든 작업을 자동으로 수행하는 것은 아니라는 것을 인식하는 것입니다. 중첩 된 객체의 작업을 수행하지 않고 계정 항목을 반환하도록 요청하면 다음과 같이 표시됩니다.
{ "id" : 6, "email" : "catchall@ukmedschool.com", "password" : "ukmedpass", "org" : "University of Kentucky College of Medicine", "role" : null }
먼저, 3-table SQL JOIN 쿼리를 생성하고 필요한 모든 데이터를 가져 왔는지 확인하십시오. 내 컨트롤러에 다음과 같이 표시됩니다.
@PreAuthorize("hasAuthority('ROLE_ADMIN')") @RequestMapping("/accounts") public List<Account> getAllAccounts3() { List<Account> accounts = jdbcTemplate.query("SELECT Account.id, Account.password, Account.org, Account.email, Account.role_for_this_account, Role.id AS roleid, Role.name AS rolename, role_views.role_id, role_views.views FROM Account JOIN Role on Account.role_for_this_account=Role.id JOIN role_views on Role.id=role_views.role_id", new AccountExtractor() {}); return accounts; }
3 개의 테이블을 조인하고 있습니다. 이제 중첩 된 객체를 함께 배치하는 RowSetExtractor 클래스를 작성하십시오. 위의 예는 2- 레이어 중첩을 보여줍니다 ...이 단계는 한 단계 더 나아가고 3 단계를 수행합니다. 두 번째 레이어 객체를지도에서도 유지 관리해야한다는 점에 유의하십시오.
public class AccountExtractor implements ResultSetExtractor<List<Account>>{ @Override public List<Account> extractData(ResultSet rs) throws SQLException, DataAccessException { Map<Long, Account> accountmap = new HashMap<Long, Account>(); Map<Long, Role> rolemap = new HashMap<Long, Role>(); // loop through the JOINed resultset. If the account ID hasn't been seen before, create a new Account object. // In either case, add the role to the account. Also maintain a map of Roles and add view (strings) to them when encountered. Set<String> views = null; while (rs.next()) { Long id = rs.getLong("id"); Account account = accountmap.get(id); if(account == null) { account = new Account(); account.setId(id); account.setPassword(rs.getString("password")); account.setEmail(rs.getString("email")); account.setOrg(rs.getString("org")); accountmap.put(id, account); } Long roleid = rs.getLong("roleid"); Role role = rolemap.get(roleid); if(role == null) { role = new Role(); role.setId(rs.getLong("roleid")); role.setName(rs.getString("rolename")); views = new HashSet<String>(); rolemap.put(roleid, role); } else { views = role.getViews(); views.add(rs.getString("views")); } views.add(rs.getString("views")); role.setViews(views); account.setRole(role); } return new ArrayList<Account>(accountmap.values()); } }
그리고 이것은 원하는 출력을 제공합니다. POJO를 참조하십시오. @ElementCollection Set 뷰를 Role 클래스에 주목하십시오. 이것은 SQL 쿼리에서 참조 된 role_views 테이블을 자동으로 생성합니다. 해당 테이블이 존재 함을 알고, 그 이름과 필드 이름은 SQL 쿼리를 올바르게 얻는 데 중요합니다. 그것은 이것이 ... 더 자동적이어야한다고 생각하는 것이 틀림 없다는 느낌이들 것입니다 - 봄이 무엇을위한 것이 아닌가? ... 그러나 나는 더 나은 길을 이해할 수 없었습니다. 내가 말할 수있는 한이 경우 수동으로 작업해야합니다.
@Entity public class Account implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy=GenerationType.AUTO) private long id; @Column(unique=true, nullable=false) private String email; @Column(nullable = false) private String password; @Column(nullable = false) private String org; private String phone; @ManyToOne(fetch = FetchType.EAGER, optional = false) @JoinColumn(name = "roleForThisAccount") // @JoinColumn means this side is the *owner* of the relationship. In general, the "many" side should be the owner, or so I read. private Role role; public Account() {} public Account(String email, String password, Role role, String org) { this.email = email; this.password = password; this.org = org; this.role = role; } // getters and setters omitted } @Entity public class Role implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy=GenerationType.AUTO) private long id; // required @Column(nullable = false) @Pattern(regexp="(ADMIN|USER)") private String name; // required @Column @ElementCollection(targetClass=String.class) private Set<String> views; @OneToMany(mappedBy="role") private List<Account> accountsWithThisRole; public Role() {} // constructor with required fields public Role(String name) { this.name = name; views = new HashSet<String>(); // both USER and ADMIN views.add("home"); views.add("viewOfferings"); views.add("viewPublicReports"); views.add("viewProducts"); views.add("orderProducts"); views.add("viewMyOrders"); views.add("viewMyData"); // ADMIN ONLY if(name.equals("ADMIN")) { views.add("viewAllOrders"); views.add("viewAllData"); views.add("manageUsers"); } } public long getId() { return this.id;} public void setId(long id) { this.id = id; }; public String getName() { return this.name; } public void setName(String name) { this.name = name; } public Set<String> getViews() { return this.views; } public void setViews(Set<String> views) { this.views = views; }; }
-
==============================
5.나는 이런 식으로 많은 일을했고 OR 매퍼 없이는 이것을 성취 할 수있는 우아한 방법을 보지 못했다.
나는 이런 식으로 많은 일을했고 OR 매퍼 없이는 이것을 성취 할 수있는 우아한 방법을 보지 못했다.
리플렉션을 기반으로하는 간단한 솔루션은 1 : 1 (또는 아마도 N : 1) 관계에 크게 의존합니다. 또한 반환 된 열은 유형에 따라 한정되지 않으므로 어떤 열이 어떤 클래스와 일치하는지 말할 수 없습니다.
스프링 데이터와 QueryDSL로 벗어날 수 있습니다. 나는 그것들을 파고 들지 않았지만 나중에 데이터베이스의 컬럼을 적절한 데이터 구조로 다시 매핑하는 데 사용되는 쿼리에 대해 일부 메타 데이터가 필요하다고 생각합니다.
유망한 새로운 postgresql json 지원을 시도해 볼 수도 있습니다.
HTH
from https://stackoverflow.com/questions/16718163/jdbctemplate-set-nested-pojo-with-beanpropertyrowmapper by cc-by-sa and MIT license
'SPRING' 카테고리의 다른 글
[SPRING] Spring MVC PATCH 메소드 : 부분 업데이트 (0) | 2019.01.11 |
---|---|
[SPRING] com.sun.jdi.InvocationException 메서드 호출 중 발생했습니다. (0) | 2019.01.11 |
[SPRING] Spring Framework의 주석을 통해 resourceBundle에서 지역화 된 메시지 가져 오기 (0) | 2019.01.11 |
[SPRING] Spring을 사용하여 2 차 레벨 캐시를 최대 절전 모드로 만듭니다. (0) | 2019.01.10 |
[SPRING] 싱글 톤을 Spring 빈으로 만드는 올바른 방법 (0) | 2019.01.10 |