복붙노트

[SPRING] JDBCTemplate은 BeanPropertyRowMapper를 사용하여 중첩 POJO를 설정합니다.

SPRING

JDBCTemplate은 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. ==============================

    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. ==============================

    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. ==============================

    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. ==============================

    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. ==============================

    5.나는 이런 식으로 많은 일을했고 OR 매퍼 없이는 이것을 성취 할 수있는 우아한 방법을 보지 못했다.

    나는 이런 식으로 많은 일을했고 OR 매퍼 없이는 이것을 성취 할 수있는 우아한 방법을 보지 못했다.

    리플렉션을 기반으로하는 간단한 솔루션은 1 : 1 (또는 아마도 N : 1) 관계에 크게 의존합니다. 또한 반환 된 열은 유형에 따라 한정되지 않으므로 어떤 열이 어떤 클래스와 일치하는지 말할 수 없습니다.

    스프링 데이터와 QueryDSL로 벗어날 수 있습니다. 나는 그것들을 파고 들지 않았지만 나중에 데이터베이스의 컬럼을 적절한 데이터 구조로 다시 매핑하는 데 사용되는 쿼리에 대해 일부 메타 데이터가 필요하다고 생각합니다.

    유망한 새로운 postgresql json 지원을 시도해 볼 수도 있습니다.

    HTH

  6. from https://stackoverflow.com/questions/16718163/jdbctemplate-set-nested-pojo-with-beanpropertyrowmapper by cc-by-sa and MIT license