2013-05-23 4 views
12

निम्न उदाहरण POJO के देखते हुए साथ नेस्टेड POJO सेट: (सभी गुण के लिए getters और setters मान लें)JDBCTemplate BeanPropertyRowMapper

class User { 
    String user_name; 
    String display_name; 
} 

class Message { 
    String title; 
    String question; 
    User user; 
} 

एक आसानी से एक डेटाबेस (मेरे मामले में postgres) क्वेरी और संदेश कक्षाओं की एक सूची को पॉप्युलेट कर सकते हैं एक बीनप्रॉपर्टी RowMapper का उपयोग करते हुए जहां डीबी क्षेत्र POJO में संपत्ति से मेल खाता है: (मान लें कि डीबी टेबल में POJO गुणों के अनुरूप फ़ील्ड हैं)।

NamedParameterDatbase.query("SELECT * FROM message", new BeanPropertyRowMapper(Message.class)); 

मैं सोच रहा हूँ - वहाँ एक भी क्वेरी बना और/या इस तरह से भी संदेश में भीतरी 'उपयोगकर्ता POJO के गुणों को भरने के लिए में एक पंक्ति नक्शाकार बनाने के लिए एक सुविधाजनक तरीका है।

है कि, कुछ syntatical जादू जहां क्वेरी में प्रत्येक परिणाम पंक्ति:

अंत में:

SELECT * FROM message, user WHERE user_id = message_id 

आबादी


प्रकरण उपयोग जुड़े उपयोगकर्ता के साथ संदेश की एक सूची तैयार , वर्गों को एक वसंत नियंत्रक से एक धारावाहिक वस्तु के रूप में वापस पारित किया जाता है, कक्षाएं घोंसला होती हैं ताकि परिणामी JSON/XML की सभ्य संरचना हो।

फिलहाल, इस स्थिति को दो प्रश्नों को निष्पादित करके हल किया गया है और मैन्युअल रूप से प्रत्येक संदेश की उपयोगकर्ता संपत्ति को लूप में सेट किया गया है। उपयोग करने योग्य, लेकिन मुझे लगता है कि एक और सुरुचिपूर्ण तरीका संभव होना चाहिए।


अद्यतन: समाधान प्रयुक्त -

कुडोस @Will कीलिंग के लिए प्रेरणा के लिए कस्टम पंक्ति नक्शाकार के उपयोग के साथ जवाब के लिए - मेरी समाधान क्रम क्षेत्र कार्य को स्वचालित करने में सेम संपत्ति नक्शे के अलावा कहते हैं ।

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 

कस्टम पंक्ति नक्शाकार फिर कई बनाता है:

चेतावनी क्वेरी की संरचना है, ताकि प्रासंगिक तालिका नामों में लगाए जाते हैं (लेकिन कोई मानक सम्मेलन इस ऐसा करने के लिए क्वेरी प्रोग्राम के बनाया गया है) बीन मानचित्र और कॉलम के उपसर्ग के आधार पर अपनी गुण सेट करता है: (कॉलम नाम प्राप्त करने के लिए मेटा डेटा का उपयोग करके)।

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; 
} 

फिर, यह एक दो वर्ग के लिए overkill की तरह लग सकता है शामिल हो सकें लेकिन IRL उपयोग के मामले क्षेत्रों के दसियों के साथ कई तालिकाओं शामिल है।

+1

उपरोक्त कोड स्निपेट टूटा हुआ है। खोलने की तुलना में अधिक बंद ब्रेसिज़ हैं। (4 बनाम 3) – fivedogit

+0

@fivedogit - अपडेट किया गया - टीवाई- आशा है कि आपको यह उपयोगी लगेगा – nclord

उत्तर

8

स्प्रिंग BeanMapper इंटरफ़ेस में एक नया AutoGrowNestedPaths संपत्ति की शुरुआत की।

जब तक SQL क्वेरी कॉलम नामों को प्रारूपित करता है। विभाजक (पहले के रूप में) तो पंक्ति मैपर स्वचालित रूप से आंतरिक वस्तुओं को लक्षित करेगा।

: QUERY:

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; 
    } 
} 
+0

का उपयोग नहीं किया है, काम नहीं करता है, कॉलम और मान सही ढंग से भरे हुए हैं, लेकिन मैप किए गए ऑब्जेक्ट को शून्य मान से भरा है। – Accollativo

2

मैंने इस तरह की चीजों पर बहुत काम किया और बिना किसी मैपर के इसे हासिल करने के लिए एक शानदार तरीका नहीं देखा।

प्रतिबिंब के आधार पर कोई भी सरल समाधान 1: 1 (या शायद एन: 1) संबंध पर निर्भर करेगा। इसके अलावा आपके कॉलम लौटाए गए हैं, उनके प्रकार से योग्य नहीं हैं, इसलिए आप यह नहीं कह सकते कि कौन से कॉलम कक्षा से मेल खाते हैं।

आप वसंत-डेटा और QueryDSL से दूर हो सकते हैं। मैंने उनमें खोद नहीं लिया, लेकिन मुझे लगता है कि आपको क्वेरी के लिए कुछ मेटा-डेटा की आवश्यकता है जिसे बाद में आपके डेटाबेस से कॉलम को उचित डेटा संरचना में मैप करने के लिए उपयोग किया जाता है।

आप नए पोस्टग्रेस्क्ल json समर्थन का भी प्रयास कर सकते हैं जो आशाजनक लग रहा है।

HTH

+0

सुझावों के लिए धन्यवाद - मैं उन्हें एक Google दूंगा और यदि वे उपयोगी साबित होते हैं तो यहां वापस लिखें। या मैपर पर विस्तार करने के किसी भी बदलाव? – nclord

+1

मैंने अभी तक इसे लागू नहीं किया है ... बस उस लड़के से बात की, जिसने वसंत-जेपीए में योगदान दिया था (यदि आप Google हैं तो पूर्व थास)। मुझे समझ में आया –

+0

लीड के लिए टीवाई - मैं भविष्य में यह समीक्षा कर सकता हूं – nclord

11

शायद आप एक कस्टम RowMapper कि (संदेश और उपयोगकर्ता के बीच) क्वेरी में शामिल होने के एक समग्र की प्रत्येक पंक्ति से मैप कर सकते एक Message और नेस्ट User को प्रेषित कर सकता है। कुछ इस तरह:

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; 
    } 
}); 
+1

सुझाव के लिए धन्यवाद - मैं प्रतिबिंब का उपयोग करने की उम्मीद कर रहा था; पोस्ट किया गया उदाहरण अल्पसंख्यक था - आईआरएल की स्थिति में पीओजेओ के विभिन्न प्रकार के क्षेत्रों के साथ है। – nclord

+1

उपरोक्त - अन्य को यह एक साधारण नेस्टेड POJO के लिए एक पंक्ति मैपर का एक उपयोगी उदाहरण मिल सकता है। – nclord

+0

कृपया मूल प्रश्न – nclord

3

अद्यतन: 2015/10/04। मैं आमतौर पर इस पंक्ति में से कोई भी नहीं करता हूं। आप एनोटेशन के माध्यम से चुनिंदा JSON प्रतिनिधित्व को और अधिक सुंदरता से पूरा कर सकते हैं। यह gist देखें।


मैं एक पूरा दिन के अधिकतर हिस्से तक खर्च 3-परत नेस्टेड वस्तुओं की मेरे मामले के लिए यह पता लगाने की कोशिश और बस अंत में इसे किसी न किसी। यहाँ मेरी स्थिति है:

लेखा (यानी उन) --1tomany -> भूमिकाएँ --1tomany -> दृश्य (उपयोगकर्ता को देखने के लिए अनुमति दी है)

(। ये POJO कक्षाएं निचले भाग पर चिपकाया जाता है)

और मैं इस तरह एक वस्तु लौटने के लिए नियंत्रक चाहता था:

[ { 
    "id" : 3, 
    "email" : "[email protected]", 
    "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" : "[email protected]", 
    "password" : "stereopass", 
    "org" : "Stereolab", 
    "role" : { 
    "id" : 1, 
    "name" : "USER", 
    "views" : [ "viewPublicReports", "viewProducts", "orderProducts", "viewOfferings", "viewMyData", "home", "viewMyOrders" ] 
    } 
}, { 
    "id" : 6, 
    "email" : "[email protected]", 
    "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" : "[email protected]", 
    "password" : "ukmedpass", 
    "org" : "University of Kentucky College of Medicine", 
    "role" : null 
} 

तो, पहले अपने 3-टेबल एसक्यूएल बनाने क्वेरी शामिल हों और सुनिश्चित करें कि आप प्राप्त कर रहे हैं आपको आवश्यक सभी डेटा। यहां मेरा है, जैसा कि यह मेरे नियंत्रक में दिखाई देता है:

@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()); 
    } 
} 

और यह वांछित आउटपुट देता है। संदर्भ के लिए नीचे POJOs। रोल क्लास में @ElementCollection सेट दृश्यों पर ध्यान दें। एसक्यूएल क्वेरी में संदर्भित रूप में यह भूमिका_दृश्य तालिका स्वचालित रूप से उत्पन्न करता है। यह तालिका जानना, 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; }; 
    } 
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 साथ याद करने के लिए महत्वपूर्ण बात यह है कि आप अपने कॉलम के नामकरण और साथ पत्र के लिए अपने वर्ग के सदस्यों के गुणों का पालन करना पड़ेगा है निम्नलिखित अपवादों (see Spring Documentation):

  • स्तंभ नाम एलियास की गई हैं वास्तव में
  • अंडरस्कोर से स्तंभ नाम "ऊंट" मामले में बदल दिया जाएगा (यानी MY_CO। LUMN_WITH_UNDERSCORES == myColumnWithUnderscores)
+0

अच्छा सुधार @ioneyed! मैं बस एक निम्नलिखित सुझाव जोड़ना चाहता हूं: - मुख्य 'MapRow' विधि के पुन: उपयोग पर बेहतर प्रदर्शन के लिए निजी अंतिम क्षेत्रों में' बीनप्रॉपर्टी RowMapper 'स्टोर करें। –

संबंधित मुद्दे