package com.oreilly.springdata.jdbc.repository; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.data.jdbc.core.OneToManyResultSetExtractor; import org.springframework.data.jdbc.query.QueryDslJdbcTemplate; import org.springframework.data.jdbc.query.SqlDeleteCallback; import org.springframework.data.jdbc.query.SqlInsertCallback; import org.springframework.data.jdbc.query.SqlInsertWithKeyCallback; import org.springframework.data.jdbc.query.SqlUpdateCallback; import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import com.mysema.query.sql.SQLQuery; import com.mysema.query.sql.dml.SQLDeleteClause; import com.mysema.query.sql.dml.SQLInsertClause; import com.mysema.query.sql.dml.SQLUpdateClause; import com.mysema.query.types.Path; import com.mysema.query.types.Predicate; import com.oreilly.springdata.jdbc.domain.Address; import com.oreilly.springdata.jdbc.domain.Customer; import com.oreilly.springdata.jdbc.domain.EmailAddress; import com.oreilly.springdata.jdbc.domain.QAddress; import com.oreilly.springdata.jdbc.domain.QCustomer; /** */ @Repository @Transactional public class QueryDslCustomerRepository implements CustomerRepository { private final QCustomer qCustomer = QCustomer.customer; private final QAddress qAddress = QAddress.address; private QueryDslJdbcTemplate qdslTemplate; private Path[] customerAddressProjection; @Autowired public void setDataSource(DataSource dataSource) { this.qdslTemplate = new QueryDslJdbcTemplate(dataSource); customerAddressProjection = new Path[] { qCustomer.id, qCustomer.firstName, qCustomer.lastName, qCustomer.emailAddress, qAddress.id, qAddress.customerId, qAddress.street, qAddress.city, qAddress.country }; } @Override @Transactional(readOnly = true) public Customer findById(Long id) { if (id == null) { return null; } return findOne(qCustomer.id.eq(id)); } @Override @Transactional(readOnly = true) public List<Customer> findAll() { SQLQuery allCustomersQuery = qdslTemplate.newSqlQuery().from(qCustomer) .leftJoin(qCustomer._addressCustomerRef, qAddress); return qdslTemplate.query(allCustomersQuery, new CustomerListExtractor(), customerAddressProjection); } @Override @Transactional(readOnly = true) public Customer findByEmailAddress(EmailAddress emailAddress) { if (emailAddress == null) { return null; } return findOne(qCustomer.emailAddress.eq(emailAddress.toString())); } private Customer findOne(Predicate predicate) { SQLQuery oneCustomerQuery = qdslTemplate.newSqlQuery().from(qCustomer) .leftJoin(qCustomer._addressCustomerRef, qAddress).where(predicate); return qdslTemplate.queryForObject(oneCustomerQuery, new CustomerExtractor(), customerAddressProjection); } @Override public void save(final Customer customer) { if (customer.getId() == null) { Long generatedKey = qdslTemplate.insertWithKey(qCustomer, new SqlInsertWithKeyCallback<Long>() { @Override public Long doInSqlInsertWithKeyClause(SQLInsertClause insert) throws SQLException { return insert .columns(qCustomer.firstName, qCustomer.lastName, qCustomer.emailAddress) .values(customer.getFirstName(), customer.getLastName(), customer.getEmailAddress() == null ? null : customer.getEmailAddress().toString()) .executeWithKey(qCustomer.id); } }); customer.setId(generatedKey); } else { qdslTemplate.update(qCustomer, new SqlUpdateCallback() { @Override public long doInSqlUpdateClause(SQLUpdateClause update) { return update .where(qCustomer.id.eq(customer.getId())) .set(qCustomer.firstName, customer.getFirstName()) .set(qCustomer.lastName, customer.getLastName()) .set(qCustomer.emailAddress, customer.getEmailAddress() == null ? null : customer.getEmailAddress().toString()).execute(); } }); } // save address data final List<Long> ids = new ArrayList<Long>(); for (Address a : customer.getAddresses()) { if (a.getId() != null) { ids.add(a.getId()); } } // first delete any potentially removed addresses if (ids.size() > 0) { qdslTemplate.delete(qAddress, new SqlDeleteCallback() { @Override public long doInSqlDeleteClause(SQLDeleteClause delete) { return delete.where(qAddress.customerId.eq(customer.getId()).and(qAddress.id.notIn(ids))).execute(); } }); } // then update existing ones and add new ones for (final Address a : customer.getAddresses()) { if (a.getId() != null) { qdslTemplate.update(qAddress, new SqlUpdateCallback() { @Override public long doInSqlUpdateClause(SQLUpdateClause update) { return update.where(qAddress.id.eq(a.getId())).set(qAddress.customerId, customer.getId()) .set(qAddress.street, a.getStreet()).set(qAddress.city, a.getCity()) .set(qAddress.country, a.getCountry()).execute(); } }); } else { qdslTemplate.insert(qAddress, new SqlInsertCallback() { @Override public long doInSqlInsertClause(SQLInsertClause insert) { return insert.columns(qAddress.customerId, qAddress.street, qAddress.city, qAddress.country) .values(customer.getId(), a.getStreet(), a.getCity(), a.getCountry()).execute(); } }); } } } @Override public void delete(final Customer customer) { qdslTemplate.delete(qAddress, new SqlDeleteCallback() { @Override public long doInSqlDeleteClause(SQLDeleteClause delete) { return delete.where(qAddress.customerId.eq(customer.getId())).execute(); } }); qdslTemplate.delete(qCustomer, new SqlDeleteCallback() { @Override public long doInSqlDeleteClause(SQLDeleteClause delete) { return delete.where(qCustomer.id.eq(customer.getId())).execute(); } }); } private static String columnLabel(Path<?> path) { return path.toString(); } private static class CustomerListExtractor extends OneToManyResultSetExtractor<Customer, Address, Integer> { private static final QCustomer qCustomer = QCustomer.customer; private final QAddress qAddress = QAddress.address; public CustomerListExtractor() { super(new CustomerMapper(), new AddressMapper()); } public CustomerListExtractor(ExpectedResults expectedResults) { super(new CustomerMapper(), new AddressMapper(), expectedResults); } @Override protected Integer mapPrimaryKey(ResultSet rs) throws SQLException { return rs.getInt(columnLabel(qCustomer.id)); } @Override protected Integer mapForeignKey(ResultSet rs) throws SQLException { String fkPath = columnLabel(qAddress.addressCustomerRef.getLocalColumns().get(0)); if (rs.getObject(fkPath) != null) { return rs.getInt(fkPath); } else { return null; } } @Override protected void addChild(Customer root, Address child) { root.addAddress(child); } } private static class CustomerExtractor implements ResultSetExtractor<Customer> { CustomerListExtractor customerListExtractor = new CustomerListExtractor( OneToManyResultSetExtractor.ExpectedResults.ONE_OR_NONE); @Override public Customer extractData(ResultSet rs) throws SQLException, DataAccessException { List<Customer> list = customerListExtractor.extractData(rs); return list.size() > 0 ? list.get(0) : null; } } private static class CustomerMapper implements RowMapper<Customer> { private static final QCustomer qCustomer = QCustomer.customer; @Override public Customer mapRow(ResultSet rs, int rowNum) throws SQLException { Customer c = new Customer(); c.setId(rs.getLong(columnLabel(qCustomer.id))); c.setFirstName(rs.getString(columnLabel(qCustomer.firstName))); c.setLastName(rs.getString(columnLabel(qCustomer.lastName))); if (rs.getString(columnLabel(qCustomer.emailAddress)) != null) { c.setEmailAddress(new EmailAddress(rs.getString(columnLabel(qCustomer.emailAddress)))); } return c; } } private static class AddressMapper implements RowMapper<Address> { private final QAddress qAddress = QAddress.address; @Override public Address mapRow(ResultSet rs, int rowNum) throws SQLException { String street = rs.getString(columnLabel(qAddress.street)); String city = rs.getString(columnLabel(qAddress.city)); String country = rs.getString(columnLabel(qAddress.country)); Address a = new Address(street, city, country); a.setId(rs.getLong(columnLabel(qAddress.id))); return a; } } }