001/* 002 * Copyright 2012-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.springframework.batch.item.data; 017 018import java.lang.reflect.InvocationTargetException; 019import java.util.List; 020 021import org.apache.commons.logging.Log; 022import org.apache.commons.logging.LogFactory; 023import org.springframework.batch.item.ItemWriter; 024import org.springframework.batch.item.adapter.AbstractMethodInvokingDelegator.InvocationTargetThrowableWrapper; 025import org.springframework.batch.item.adapter.DynamicMethodInvocationException; 026import org.springframework.beans.factory.InitializingBean; 027import org.springframework.data.repository.CrudRepository; 028import org.springframework.util.Assert; 029import org.springframework.util.CollectionUtils; 030import org.springframework.util.MethodInvoker; 031 032/** 033 * <p> 034 * A {@link org.springframework.batch.item.ItemReader} wrapper for a 035 * {@link org.springframework.data.repository.CrudRepository} from Spring Data. 036 * </p> 037 * 038 * <p> 039 * It depends on {@link org.springframework.data.repository.CrudRepository#saveAll(Iterable)} 040 * method to store the items for the chunk. Performance will be determined by that 041 * implementation more than this writer. 042 * </p> 043 * 044 * <p> 045 * As long as the repository provided is thread-safe, this writer is also thread-safe once 046 * properties are set (normal singleton behavior), so it can be used in multiple concurrent 047 * transactions. 048 * </p> 049 * 050 * <p> 051 * NOTE: The {@code RepositoryItemWriter} only stores Java Objects i.e. non primitives. 052 * </p> 053 * 054 * @author Michael Minella 055 * @author Mahmoud Ben Hassine 056 * @since 2.2 057 */ 058public class RepositoryItemWriter<T> implements ItemWriter<T>, InitializingBean { 059 060 protected static final Log logger = LogFactory.getLog(RepositoryItemWriter.class); 061 062 private CrudRepository<T, ?> repository; 063 064 private String methodName; 065 066 /** 067 * Specifies what method on the repository to call. This method must have the type of 068 * object passed to this writer as the <em>sole</em> argument. 069 * 070 * @param methodName {@link String} containing the method name. 071 */ 072 public void setMethodName(String methodName) { 073 this.methodName = methodName; 074 } 075 076 /** 077 * Set the {@link org.springframework.data.repository.CrudRepository} implementation 078 * for persistence 079 * 080 * @param repository the Spring Data repository to be set 081 */ 082 public void setRepository(CrudRepository<T, ?> repository) { 083 this.repository = repository; 084 } 085 086 /** 087 * Write all items to the data store via a Spring Data repository. 088 * 089 * @see org.springframework.batch.item.ItemWriter#write(java.util.List) 090 */ 091 @Override 092 public void write(List<? extends T> items) throws Exception { 093 if(!CollectionUtils.isEmpty(items)) { 094 doWrite(items); 095 } 096 } 097 098 /** 099 * Performs the actual write to the repository. This can be overridden by 100 * a subclass if necessary. 101 * 102 * @param items the list of items to be persisted. 103 * 104 * @throws Exception thrown if error occurs during writing. 105 */ 106 protected void doWrite(List<? extends T> items) throws Exception { 107 if (logger.isDebugEnabled()) { 108 logger.debug("Writing to the repository with " + items.size() + " items."); 109 } 110 111 MethodInvoker invoker = createMethodInvoker(repository, methodName); 112 113 for (T object : items) { 114 invoker.setArguments(new Object [] {object}); 115 doInvoke(invoker); 116 } 117 } 118 119 /** 120 * Check mandatory properties - there must be a repository. 121 */ 122 @Override 123 public void afterPropertiesSet() throws Exception { 124 Assert.state(repository != null, "A CrudRepository implementation is required"); 125 } 126 127 128 private Object doInvoke(MethodInvoker invoker) throws Exception{ 129 try { 130 invoker.prepare(); 131 } 132 catch (ClassNotFoundException e) { 133 throw new DynamicMethodInvocationException(e); 134 } 135 catch (NoSuchMethodException e) { 136 throw new DynamicMethodInvocationException(e); 137 } 138 139 try { 140 return invoker.invoke(); 141 } 142 catch (InvocationTargetException e) { 143 if (e.getCause() instanceof Exception) { 144 throw (Exception) e.getCause(); 145 } 146 else { 147 throw new InvocationTargetThrowableWrapper(e.getCause()); 148 } 149 } 150 catch (IllegalAccessException e) { 151 throw new DynamicMethodInvocationException(e); 152 } 153 } 154 155 private MethodInvoker createMethodInvoker(Object targetObject, String targetMethod) { 156 MethodInvoker invoker = new MethodInvoker(); 157 invoker.setTargetObject(targetObject); 158 invoker.setTargetMethod(targetMethod); 159 return invoker; 160 } 161}