How to write nested XML to database using spring batch? - mysql
I have an XML file data . and my target to put it in mysql database with a table called forecast containing just four columns (area_id, parameter_id, datetime, and value).
I managed to read the data from all the XML data but couldn't write the data into the database yet. How to write data that has been read in the logging class to the database?
below, there are several classes that I created with a spring batch configuration
Area.java
#XmlRootElement(name="area")
#XmlAccessorType(XmlAccessType.FIELD)
public class Area {
#XmlAttribute(name = "id")
private String area_id;
#XmlElement(name = "parameter")
private List<Parameter> area=null;
public List<Parameter> getArea(){
return area;
}
public void setArea(List<Parameter> area){
this.area = area;
}
public Area(){
}
public Area(String area_id) {
this.area_id = area_id;
}
#Override
public String toString() {
return "Area{" +
"area_id='" + area_id + '\'' +
", area=" + area +
'}';
}
}
TimeRange.java
#XmlRootElement(name="timerange")
#XmlAccessorType(XmlAccessType.FIELD)
public class TimeRange{
#XmlElement(name = "value")
String value;
#XmlAttribute(name = "datetime")
String datetime;
public TimeRange(){
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getDatetime() {
return datetime;
}
public void setDatetime(String datetime) {
this.datetime = datetime;
}
#Override
public String toString() {
final StringBuffer sb = new StringBuffer("TimeRange{");
sb.append("value='").append(value).append('\'');
sb.append(", datetime='").append(datetime).append('\'');
sb.append('}');
return sb.toString();
}
}
Parameter.java
#XmlRootElement(name="parameter")
#XmlAccessorType(XmlAccessType.FIELD)
public class Parameter{
#XmlAttribute(name="id")
private String parameter_id;
#XmlElement(name="timerange")
public List<TimeRange> parameter;
public List<TimeRange> getParameter(){
return parameter;
}
public void setParameter(List<TimeRange> parameter){
this.parameter = parameter;
}
public Parameter(){
}
public Parameter(String parameter_id) {
this.parameter_id = parameter_id;
}
#Override
public String toString() {
final StringBuffer sb = new StringBuffer("Parameter{");
sb.append("parameter_id='").append(parameter_id).append('\'');
sb.append(", parameter=").append(parameter);
sb.append('}');
return sb.toString();
}
}
Config.java
#Configuration
public class SpringBatchExampleJobConfig {
#Bean
public ItemReader<Area> areaItemReader() {
Jaxb2Marshaller areaMarshaller = new Jaxb2Marshaller();
areaMarshaller.setClassesToBeBound(Area.class);
return new StaxEventItemReaderBuilder<Area>()
.name("areaReader")
.resource(new ClassPathResource("data/DigitalForecast-SulawesiTengah.xml"))
.addFragmentRootElements("area")
.unmarshaller(areaMarshaller)
.build();
}
#Bean
public ItemWriter<Area> areaItemWriter(){
return new LoggingAreaItemWriter();
}
/**
* Creates a bean that represents the only step of our batch job.
* #param areaItemReader
* #param step1BuilderFactory
* #param areaItemWriter
* #return
*/
#Bean
public Step Step1(ItemReader<Area> areaItemReader,
ItemWriter<Area> areaItemWriter,
StepBuilderFactory step1BuilderFactory) {
return step1BuilderFactory.get("Step1")
.<Area, Area>chunk(1)
.reader(areaItemReader)
.writer(areaItemWriter)
.build();
}
/**
* Creates a bean that represents our example batch job.
* #param Step1
* #param jobBuilderFactory
* #return
*/
#Bean
public Job areaJob(Step Step1,
JobBuilderFactory jobBuilderFactory) {
return jobBuilderFactory.get("areaJob")
.incrementer(new RunIdIncrementer())
.start(Step1)
.build();
}
}
and the logging class I use to write readable data results
LoggingAreaItemWriter.java
public class LoggingAreaItemWriter implements ItemWriter<Area> {
private static final Logger LOGGER = LoggerFactory.getLogger(LoggingAreaItemWriter.class);
#Override
public void write(List<? extends Area> list) throws Exception {
LOGGER.info("Writing area: {}", list);
}
}
and the result
Writing area: [Area{area_id='501520', area=[Parameter{parameter_id='hu', parameter=[TimeRange{value='95', datetime='202102010000'}, TimeRange{value='80', datetime='202102010600'}, TimeRange{value='90', datetime='202102011200'}, TimeRange{value='95', datetime='202102011800'}, TimeRange{value='85', datetime='202102020000'}, TimeRange{value='60', datetime='202102020600'}, TimeRange{value='90', datetime='202102021200'}, TimeRange{value='90', datetime='202102021800'}, TimeRange{value='85', datetime='202102030000'}, TimeRange{value='55', datetime='202102030600'}, TimeRange{value='95', datetime='202102031200'}, TimeRange{value='95', datetime='202102031800'}]}, Parameter{parameter_id='humax', parameter=[TimeRange{value='95', datetime='202102011200'}, TimeRange{value='95', datetime='202102021200'}, TimeRange{value='95', datetime='202102031200'}]}, Parameter{parameter_id='tmax', parameter=[TimeRange{value='91.4', datetime='202102011200'}, TimeRange{value='91.4', datetime='202102021200'}, TimeRange{value='91.4', datetime='202102031200'}]}, Parameter{parameter_id='humin', parameter=[TimeRange{value='80', datetime='202102011200'}, TimeRange{value='60', datetime='202102021200'}, TimeRange{value='55', datetime='202102031200'}]}, Parameter{parameter_id='tmin', parameter=[TimeRange{value='75.2', datetime='202102011200'}, TimeRange{value='73.4', datetime='202102021200'}, TimeRange{value='73.4', datetime='202102031200'}]}, Parameter{parameter_id='t', parameter=[TimeRange{value='75.2', datetime='202102010000'}, TimeRange{value='91.4', datetime='202102010600'}, TimeRange{value='75.2', datetime='202102011200'}, TimeRange{value='73.4', datetime='202102011800'}, TimeRange{value='77', datetime='202102020000'}, TimeRange{value='91.4', datetime='202102020600'}, TimeRange{value='77', datetime='202102021200'}, TimeRange{value='75.2', datetime='202102021800'}, TimeRange{value='78.8', datetime='202102030000'}, TimeRange{value='91.4', datetime='202102030600'}, TimeRange{value='77', datetime='202102031200'}, TimeRange{value='73.4', datetime='202102031800'}]}, Parameter{parameter_id='weather', parameter=[TimeRange{value='1', datetime='202102010000'}, TimeRange{value='1', datetime='202102010600'}, TimeRange{value='1', datetime='202102011200'}, TimeRange{value='3', datetime='202102011800'}, TimeRange{value='1', datetime='202102020000'}, TimeRange{value='1', datetime='202102020600'}, TimeRange{value='1', datetime='202102021200'}, TimeRange{value='3', datetime='202102021800'}, TimeRange{value='3', datetime='202102030000'}, TimeRange{value='1', datetime='202102030600'}, TimeRange{value='0', datetime='202102031200'}, TimeRange{value='3', datetime='202102031800'}]}, Parameter{parameter_id='wd', parameter=[TimeRange{value='29230', datetime='202102010000'}, TimeRange{value='4500', datetime='202102010600'}, TimeRange{value='2230', datetime='202102011200'}, TimeRange{value='22500', datetime='202102011800'}, TimeRange{value='22500', datetime='202102020000'}, TimeRange{value='4500', datetime='202102020600'}, TimeRange{value='15730', datetime='202102021200'}, TimeRange{value='22500', datetime='202102021800'}, TimeRange{value='27000', datetime='202102030000'}, TimeRange{value='000', datetime='202102030600'}, TimeRange{value='000', datetime='202102031200'}, TimeRange{value='27000', datetime='202102031800'}]}, Parameter{parameter_id='ws', parameter=[TimeRange{value='1.028888888', datetime='202102010000'}, TimeRange{value='10.28888888', datetime='202102010600'}, TimeRange{value='1.028888888', datetime='202102011200'}, TimeRange{value='1.028888888', datetime='202102011800'}, TimeRange{value='1.028888888', datetime='202102020000'}, TimeRange{value='10.28888888', datetime='202102020600'}, TimeRange{value='1.028888888', datetime='202102021200'}, TimeRange{value='1.028888888', datetime='202102021800'}, TimeRange{value='1.028888888', datetime='202102030000'}, TimeRange{value='0', datetime='202102030600'}, TimeRange{value='0', datetime='202102031200'}, TimeRange{value='2.57222222', datetime='202102031800'}]}]}]
Please add a JDBC writer instead of logging writer like below.
#Bean
public JdbcBatchItemWriter<UnifiedInvoiceDTO> summaryWriter() {
JdbcBatchItemWriter<UnifiedInvoiceDTO> databaseItemWriter = new JdbcBatchItemWriter<>();
databaseItemWriter.setDataSource(dataSource);
databaseItemWriter.setSql("YOUR SQL");
ItemPreparedStatementSetter<UnifiedInvoiceDTO> invoicePreparedStatementSetter = new InvoiceSummarySetter();
databaseItemWriter.setItemPreparedStatementSetter(invoicePreparedStatementSetter);
return databaseItemWriter;
}
public class InvoiceSummarySetter implements ItemPreparedStatementSetter<UnifiedInvoiceDTO> {
Logger logger = LoggerFactory.getLogger(InvoiceSummarySetter.class);
#Override
public void setValues(UnifiedInvoiceDTO invoiceDTO, PreparedStatement preparedStatement) throws SQLException {
preparedStatement.setString(1, invoiceDTO.getTypeInvInfo().getAccountNumber());
}
Your XML input has a hierarchical format:
<area id="1">
<parameter id="11">
<timerange datetime="202102220000">
<value>85</value>
</timerange>
</>
<parameter id="12">
<timerange datetime="202102220600">
<value>95</value>
</timerange>
</>
</area>
<area id="2">
<parameter id="21">
<timerange datetime="202102220000">
<value>85</value>
</timerange>
</>
<parameter id="22">
<timerange datetime="202102220600">
<value>95</value>
</timerange>
</>
</area>
while your output has a flat format area_id,parameter_id,datetime,value:
1,11,202102220000,85
1,12,202102220600,95
2,21,202102220000,85
2,22,202102220600,95
So a single input XML item should be written as multiple database records. In this case, you need to "flatmap" your items before writing them to the database. This can be done with an item processor. See the following posts for more details on how to do that:
Save object with List as csv file FlatFileItemWriter
Flatten processing result in spring batch
Related
problem navigating from app to baseview model with this base ctor
I have a base class on baseviewmodel. I'm facing navigationservice implemented on 6.2 on debug shows problem navigating to another viewmodel. debug shows userdialogs break. Is there a problem using base class in this way with those parameters . anyone faced this kind of issue public BaseViewModel(IMvxNavigationService navigationService, ILoginService loginService, UserDialogs userDialogs, IValidator validator) { _navigationService = navigationService; _loginService = loginService; _userDialogs = userDialogs; _validator = validator; Title = TextSource.GetText(StringResourceKeys.Title); IsBusyMessage = Resources.Strings.LoadingMesssage; } using gettext provider like this public class ResourcexTextProvider : IMvxTextProvider { private readonly ResourceManager _resourceManager; public ResourcexTextProvider(ResourceManager resourceManager) { _resourceManager = resourceManager; CurrentLanguage = CultureInfo.CurrentUICulture; } public CultureInfo CurrentLanguage { get; set; } public string GetText(string namespaceKey, string typeKey, string name) { string resolvedKey = name; if (!string.IsNullOrEmpty(typeKey)) { resolvedKey = $"{typeKey}.{resolvedKey}"; } if (!string.IsNullOrEmpty(namespaceKey)) { resolvedKey = $"{namespaceKey}.{resolvedKey}"; } return _resourceManager.GetString(resolvedKey, CurrentLanguage); } public string GetText(string namespaceKey, string typeKey, string name, params object[] formatArgs) { string baseText = GetText(namespaceKey, typeKey, name); if (string.IsNullOrEmpty(baseText)) { return baseText; } return string.Format(baseText, formatArgs); } public bool TryGetText(out string textValue, string namespaceKey, string typeKey, string name) { throw new System.NotImplementedException(); } public bool TryGetText(out string textValue, string namespaceKey, string typeKey, string name, params object[] formatArgs) { throw new System.NotImplementedException(); } } }
You're trying to inject UserDialogs userDialogs in the ctor of your BaseViewModel. My guess is that you missed registering the userDialogs. First of all you should inject interfaces instead of implementations to improve maintainability: Mvx.IocConstruct.RegisterType<IUserDialogs, UserDialogs>(); And if my guess is correct and you are using Acr.UserDialogs you should initialize it and register it as: Mvx.IoCProvider.RegisterSingleton<IUserDialogs>(() => UserDialogs.Instance); Then you can inject it in any ViewModel directly using the interface: public BaseViewModel(IMvxNavigationService navigationService, ILoginService loginService, IUserDialogs userDialogs, IValidator validator) { _navigationService = navigationService; _loginService = loginService; _userDialogs = userDialogs; _validator = validator; Title = TextSource.GetText(StringResourceKeys.Title); IsBusyMessage = Resources.Strings.LoadingMesssage; } HIH
Upload pdf in HTML and Deserialize json file
I'm trying to upload a file in html and then send it to my database via restangular. My frontend is a combination of angular with typescript but the upload is a form. <form enctype="multipart/form-data"> <fieldset class="form-group" ng-repeat="field in $ctrl.metadata.fields"> <label ng-if="field.inputType !== 'hidden'" for="{{field.propertyKey}}"><strong>{{field.name}}</strong></label> <input ng-if="field.inputType !== 'select' && field.inputType !== 'file'" class="form-control" type="{{field.inputType}}" name="{{field.propertyKey}}" id="{{field.propertyKey}}" ng-model="$ctrl.data[field.propertyKey]"/> <input ng-if="field.inputType === 'file'" class="form-control" ngf-select type="{{field.inputType}}" name="{{field.propertyKey}}" id="{{field.propertyKey}}" ng-model="$ctrl.data[field.propertyKey]"/> <sp-dropdown ng-if="field.inputType === 'select'" value="$ctrl.data[field.propertyKey]" api-domain="field.linkedObjectApiDomain" linked-object-name="field.linkedObjectName"></sp-dropdown> </fieldset> <button class="btn btn-primary" ng-click="$ctrl.save({item: $ctrl.data})">Save</button> <button ng-if="$ctrl.metadata.buttons.hasOpen" class="btn btn-primary" ng-click="$ctrl.open()">Open</button> </form> I did the databinding of the file with ng-file-upload. Upon saving we enter this typescript save method. public save(item: any): any { console.log("item to save is ", item); console.log("rapport is ", item["rapport"]); if (item.id === undefined) { this.restService.save(this.metadata.apiDomain, item).then((addedItem: any) => { toastr.success(`${addedItem.naam} successfully created.`, `Overzicht Dossiers Created`); }); } else { this.restService.update(this.metadata.apiDomain, item).then((updatedItem: any) => { toastr.success(`${updatedItem.naam} successfully updated.`, `Overzicht Dossiers Updated`); }); } } The second log with the file gives the json: lastModified:1463402787393 lastModifiedDate:Mon May 16 2016 14:46:27 GMT+0200 (Romance (zomertijd)) name:"Rapport.pdf" size:83605 type:"application/pdf" upload:Promise webkitRelativePath:"" __proto__:File On server side I'm using a spring project which I didn't set up myself but the important files are my class which should store this data Dossier /* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package be.ugent.lca.data.entities; import be.ugent.sherpa.entity.BaseEntity; import java.sql.Date; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.JoinColumn; import javax.persistence.Lob; import javax.persistence.ManyToOne; import javax.persistence.OneToOne; /** * * #author Sam */ #Entity //#JsonDeserialize(using = DossierDeserializer.class) //#JsonSerialize(using = DossierSerializer.class) public class Dossier extends BaseEntity{ private String externDossierNr; private String internDossierNr; private Date datum; private Boolean doc; private Date refKlantDatum; private String refKlantVerwijzing; private String verantw; #OneToOne(fetch=FetchType.LAZY, mappedBy="dossier") private Offerte offerte; private String status; #ManyToOne(fetch=FetchType.EAGER) #JoinColumn(name = "persoon") private Persoon persoon; #ManyToOne(fetch = FetchType.LAZY) #JoinColumn(name = "OrganisatieFirma") private OrganisatieFirma organisatieFirma; #ManyToOne(fetch = FetchType.LAZY) #JoinColumn(name = "OrganisatieIntern") private OrganisatieIntern organisatieIntern; #Lob #Column(length=100000) private byte[] rapport; public Offerte getOfferte() { return offerte; } public void setOfferte(Offerte offerte) { this.offerte = offerte; } public byte[] getRapport() { return rapport; } public void setRapport(byte[] rapport) { this.rapport = rapport; } public OrganisatieFirma getOrganisatieFirma() { return organisatieFirma; } public String getExternDossierNr() { return externDossierNr; } public void setExternDossierNr(String externDossierNr) { this.externDossierNr = externDossierNr; } public String getInternDossierNr() { return internDossierNr; } public void setInternDossierNr(String internDossierNr) { this.internDossierNr = internDossierNr; } public void setOrganisatieFirma(OrganisatieFirma organisatieFirma) { this.organisatieFirma = organisatieFirma; } public OrganisatieIntern getOrganisatieIntern() { return organisatieIntern; } public void setOrganisatieIntern(OrganisatieIntern organisatieIntern) { this.organisatieIntern = organisatieIntern; } public Persoon getPersoon() { return persoon; } public void setPersoon(Persoon persoon) { this.persoon = persoon; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public Date getDatum() { return datum; } public void setDatum(Date datum) { this.datum = datum; } public Date getRefKlantDatum() { return refKlantDatum; } public void setRefKlantDatum(Date refKlantDatum) { this.refKlantDatum = refKlantDatum; } public String getRefKlantVerwijzing() { return refKlantVerwijzing; } public void setRefKlantVerwijzing(String refKlantVerwijzing) { this.refKlantVerwijzing = refKlantVerwijzing; } public String getVerantw() { return verantw; } public void setVerantw(String verantw) { this.verantw = verantw; } public Boolean getDoc() { return doc; } public void setDoc(Boolean doc) { this.doc = doc; } } and my repository for this class /* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package be.ugent.lca.data.repository; import be.ugent.lca.data.entities.Dossier; import be.ugent.lca.data.query.DossierQuery; import be.ugent.sherpa.repository.RestRepository; import org.springframework.data.rest.core.annotation.RepositoryRestResource; /** * * #author Sam */ #RepositoryRestResource(collectionResourceRel = "dossiers", path = "dossiers") public interface DossierRepository extends RestRepository<Dossier, DossierQuery<?>>{ } When trying to save a file to my database the server gives this exception Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of byte[] out of START_OBJECT token This led me to believe that I have to write my own deserializer for Dossier Thus: package be.ugent.lca.data.entities.deserializers; import be.ugent.lca.data.entities.Dossier; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; import java.io.IOException; public class DossierDeserializer extends JsonDeserializer { #Override public Dossier deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { ObjectCodec oc = jsonParser.getCodec(); JsonNode root = oc.readTree(jsonParser); Dossier dossier = new Dossier(); dossier.setExternDossierNr(root.get("externDossierNr").asText()); dossier.setInternDossierNr(root.get("internDossierNr").asText()); return dossier; } } But my problem is that I don't know how exactly to deserialize the file json, since writing out root.get("rapport") gives back an empty string. Any help would be much appreciated.
I've worked out the file upload. First of all I split the file upload from the rest of my data so I won't have to rewrite the automatic deserialization for everything that does work. this.restService.save(this.metadata.apiDomain, item).then((addedItem: any) => { toastr.success(`${addedItem.naam} successfully created.`, `Overzicht Dossiers Created`); console.log("created item ", addedItem); var fd = new FormData(); fd.append("rapport", item["rapport"]); this.restService.one('dossiers/' + addedItem.id + '/rapport').withHttpConfig({transformRequest: angular.identity}).customPOST(fd, '', undefined, {'Content-Type': undefined}).then( (addedDossier: any) => { console.log("posted dossier ", addedDossier); } ); }); In the callback of my normal save I do the custom post to dossiers/{id}/rapport for this I need a custom controller. #BasePathAwareController #RequestMapping("/dossiers/{id}") #ExposesResourceFor(Dossier.class) public class DossierController { The BasePathAwawareController makes sure that all automatically generated paths that you don't override keep existing. #Autowired private DossierRepository dossierRepository; With this I inject my repository to connect to my database. #RequestMapping(path = "/rapport", method = RequestMethod.POST)//,headers = "content-type=multipart/form-data") public #ResponseBody String postRapport(#PathVariable("id") Long id,#RequestParam("rapport") MultipartFile file) { String name = "rapport"; System.out.println("Entered custom file upload with id " + id); if (!file.isEmpty()) { try { byte[] bytes = file.getBytes(); Dossier dossier = dossierRepository.findOne(id); dossier.setRapport(bytes); dossierRepository.save(dossier); return "You successfully uploaded " + name + " into " + name + "-uploaded !"; } catch (Exception e) { return "You failed to upload " + name + " => " + e.getMessage(); } } else { return "You failed to upload " + name + " because the file was empty."; } } Like this I'm able to successfully upload my file.
gson flat json to nested objects needs serializer/deserializer?
I have some JSON coming in (I don't have any control or ability to change the structure and/or naming within the JSON...important to keep in mind in this question) that has a "flat" structure similar to this: { "name": "...", "email": "...", "box_background_color": "...", "box_border_color": "...", "box_text_color": "...", ... } Now, I can just create a simple object that keeps everything flat, like so: public class Settings { #SerializedName("name") private String _name; #SerializedName("email") private String _emailAddress; #SerializedName("box_background_color") private String _boxBackgroundColor; #SerializedName("box_border_color") private String _boxBorderColor; #SerializedName("box_text_color") private String _boxTextColor; ... } However, I want everything associated with box settings to be in it's own class (BoxSettings). This is more like what I want: public class Settings { #SerializedName("name") private String _name; #SerializedName("email") private String _emailAddress; private BoxSettings _boxSettings ... } public class BoxSettings { #SerializedName("box_background_color") private String _boxBackgroundColor; #SerializedName("box_border_color") private String _boxBorderColor; #SerializedName("box_text_color") private String _boxTextColor; ... } I know that if the JSON was structured such that the box settings were nested then it would be easy to accomplish what I want, however, I don't have the ability to change the structure of the JSON, so please don't suggest that (I would do it if I could). My question is this: Is creating an entire TypeAdapter the only way to accomplish what I want or can I still accomplish most of this with annotations? If it is not the only way, how else can I accomplish this without changing the JSON at all? The following is an example of what I mean by "creating an entire TypeAdapter": public class SettingsTypeAdapter implements JsonDeserializer<Settings>, JsonSerializer<Settings> { #Override public JsonElement serialize(Settings src, Type typeOfSrc, JsonSerializationContext context) { // Add _name // Add _emailAddress // Add BoxSettings._boxBackgroundColor // Add BoxSettings._boxBorderColor // Add BoxSettings._boxTextColor return jsonElement; } #Override public Settings deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { // Read _name // Read _emailAddress // Read BoxSettings._boxBackgroundColor // Read BoxSettings._boxBorderColor // Read BoxSettings._boxTextColor return settings; } }
The TypeAdapter is not the only way, but in this case would be the best way since you can associate the adapter with a Gson instance (or whatever library you are using) and have all your mapping code there. Another way is to use JAVA reflection. I've used a version of the below code in my projects before but never with JSON and never with nested objects (mostly when there was no other choice or if i wanted to map a SQL result set to a Java object without calling resultSet.get... a lot of times). This will work in this case. import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.json.JSONObject; public class Main { public static void main(String[] args) { try { String json = "{\"name\": \"test name\", \"email\": \"email#email.com\", \"box_background_color\": \"red\", \"box_border_color\": \"orange\", \"box_text_color\": \"white\", \"test3_var2\":3}"; JSONObject jsonObject = new JSONObject(json); System.out.println(jsonObject); System.out.println(); /* * need to parse JSON into a map of String, Object */ Map<String, Object> mapAll = new HashMap<String, Object>(); Iterator<String> iter = jsonObject.keys(); while (iter.hasNext()) { String key = (String) iter.next(); Object value = jsonObject.get(key); mapAll.put(key, value); System.out.println(key + "::::" + value); } System.out.println(); /* * use the mapper to generate the objects */ MyMapper<TestClass1> myMapper = new MyMapper<TestClass1>(); TestClass1 result = myMapper.mapToObject(mapAll, TestClass1.class); System.out.println(result); } catch (Exception e) { e.printStackTrace(); } } } class MyMapper<T> { #SuppressWarnings("unchecked") public T mapToObject(Map<String, Object> flatStructure, Class<T> objectClass) { T result = null; Field[] fields = null; try { // new base object result = objectClass.newInstance(); // get all of its fields fields = objectClass.getDeclaredFields(); for (Field field : fields) { // normal variable if (field.isAnnotationPresent(MyColumn.class)) { String variableKey = field.getAnnotation(MyColumn.class).variableKey(); setJavaFieldValue(result, field.getName(), flatStructure.get(variableKey)); } // variable that is an object and itself has to be mapped else if (field.isAnnotationPresent(MyInnerColumn.class)) { String startsWith = field.getAnnotation(MyInnerColumn.class).startsWith(); // reduce the map to only have attributes that are related to this field Map<String, Object> reducedMap = reduceMap(startsWith, flatStructure); // make sure that there are attributes for the inner object if (reducedMap != null) { // map the inner object MyMapper<T> myMapper = new MyMapper<T>(); T t2 = myMapper.mapToObject(reducedMap, (Class<T>) field.getType()); // set the mapped object to the base objecct setJavaFieldValue(result, field.getName(), t2); } } else { // no annotation on the field so ignored } } } catch (Exception e) { e.printStackTrace(); } return result; } private Map<String, Object> reduceMap(String startsWith, Map<String, Object> mapToReduce) { Map<String, Object> result = new HashMap<String, Object>(); for (Map.Entry<String, Object> entry : mapToReduce.entrySet()) { if (entry.getKey().toLowerCase().startsWith(startsWith.toLowerCase())) { result.put(entry.getKey(), entry.getValue()); } } return result.size() == 0 ? null : result; } private void setJavaFieldValue(Object object, String fieldName, Object fieldValue) { try { Field field = object.getClass().getDeclaredField(fieldName); boolean fieldAccess = field.isAccessible(); // make the field accessible field.setAccessible(true); field.set(object, fieldValue); // put it back to the way it was field.setAccessible(fieldAccess); } catch (Exception e) { e.printStackTrace(); } } } /* * Annotation for a regular variable / field */ #Target(ElementType.FIELD) #Retention(RetentionPolicy.RUNTIME) #interface MyColumn { // the variable's JSON key String variableKey() default ""; } /* * Annotation for an inner / nested variable / field */ #Target(ElementType.FIELD) #Retention(RetentionPolicy.RUNTIME) #interface MyInnerColumn { /* * JSON keys that start with this string will be * associated with this nested field */ String startsWith() default ""; } class TestClass1 { #MyColumn(variableKey = "name") private String _name; #MyColumn(variableKey = "email") private String _emailAddress; #MyInnerColumn(startsWith = "box_") private TestClass2 innerClass; #MyInnerColumn(startsWith = "test3_") private TestClass3 innerClass2; #Override public String toString() { return "TestClass1 [_name=" + _name + ", _emailAddress=" + _emailAddress + ", innerClass=" + innerClass + ", innerClass2=" + innerClass2 + "]"; } } class TestClass2 { #MyColumn(variableKey = "box_background_color") private String _boxBackgroundColor; #MyColumn(variableKey = "box_border_color") private String _boxBorderColor; #MyColumn(variableKey = "box_text_color") private String _boxTextColor; #Override public String toString() { return "TestClass2 [_boxBackgroundColor=" + _boxBackgroundColor + ", _boxBorderColor=" + _boxBorderColor + ", _boxTextColor=" + _boxTextColor + "]"; } } class TestClass3 { #MyColumn(variableKey = "test3_var1") private String _test3Var1; #MyColumn(variableKey = "test3_var2") private int _test3Var2; #Override public String toString() { return "TestClass3 [_test3Var1=" + _test3Var1 + ", _test3Var2=" + _test3Var2 + "]"; } } Output {"box_background_color":"red","box_text_color":"white","test3_var2":3,"name":"test name","email":"email#email.com","box_border_color":"orange"} box_background_color::::red box_text_color::::white test3_var2::::3 name::::test name email::::email#email.com box_border_color::::orange TestClass1 [_name=test name, _emailAddress=email#email.com, innerClass=TestClass2 [_boxBackgroundColor=red, _boxBorderColor=orange, _boxTextColor=white], innerClass2=TestClass3 [_test3Var1=null, _test3Var2=3]]
how to save apache spark schema output in mysql database
Can anyone please tell me if there is any way in apache spark to store a JavaRDD on mysql database? I am taking input from 2 csv files and then after doing join operations on their contents I need to save the output(the output JavaRDD) in the mysql database. I am already able to save the output successfully on hdfs but I am not finding any information related to apache Spark-MYSQL connection. Below I am posting the code for spark sql. This might serve as a reference to those who are looking for an example for spark-sql. package attempt1; import java.io.Serializable; import org.apache.spark.api.java.JavaRDD; import org.apache.spark.api.java.JavaSparkContext; import org.apache.spark.api.java.function.Function; import org.apache.spark.sql.api.java.JavaSQLContext; import org.apache.spark.sql.api.java.JavaSchemaRDD; import org.apache.spark.sql.api.java.Row; public class Spark_Mysql { #SuppressWarnings("serial") public static class CompleteSample implements Serializable { private String ASSETNUM; private String ASSETTAG; private String CALNUM; public String getASSETNUM() { return ASSETNUM; } public void setASSETNUM(String aSSETNUM) { ASSETNUM = aSSETNUM; } public String getASSETTAG() { return ASSETTAG; } public void setASSETTAG(String aSSETTAG) { ASSETTAG = aSSETTAG; } public String getCALNUM() { return CALNUM; } public void setCALNUM(String cALNUM) { CALNUM = cALNUM; } } #SuppressWarnings("serial") public static class ExtendedSample implements Serializable { private String ASSETNUM; private String CHANGEBY; private String CHANGEDATE; public String getASSETNUM() { return ASSETNUM; } public void setASSETNUM(String aSSETNUM) { ASSETNUM = aSSETNUM; } public String getCHANGEBY() { return CHANGEBY; } public void setCHANGEBY(String cHANGEBY) { CHANGEBY = cHANGEBY; } public String getCHANGEDATE() { return CHANGEDATE; } public void setCHANGEDATE(String cHANGEDATE) { CHANGEDATE = cHANGEDATE; } } #SuppressWarnings("serial") public static void main(String[] args) throws Exception { JavaSparkContext ctx = new JavaSparkContext("local[2]", "JavaSparkSQL"); JavaSQLContext sqlCtx = new JavaSQLContext(ctx); JavaRDD<CompleteSample> cs = ctx.textFile("C:/Users/cyg_server/Documents/bigDataExample/AssetsImportCompleteSample.csv").map( new Function<String, CompleteSample>() { public CompleteSample call(String line) throws Exception { String[] parts = line.split(","); CompleteSample cs = new CompleteSample(); cs.setASSETNUM(parts[0]); cs.setASSETTAG(parts[1]); cs.setCALNUM(parts[2]); return cs; } }); JavaRDD<ExtendedSample> es = ctx.textFile("C:/Users/cyg_server/Documents/bigDataExample/AssetsImportExtendedSample.csv").map( new Function<String, ExtendedSample>() { public ExtendedSample call(String line) throws Exception { String[] parts = line.split(","); ExtendedSample es = new ExtendedSample(); es.setASSETNUM(parts[0]); es.setCHANGEBY(parts[1]); es.setCHANGEDATE(parts[2]); return es; } }); JavaSchemaRDD complete = sqlCtx.applySchema(cs, CompleteSample.class); complete.registerAsTable("cs"); JavaSchemaRDD extended = sqlCtx.applySchema(es, ExtendedSample.class); extended.registerAsTable("es"); JavaSchemaRDD fs= sqlCtx.sql("SELECT cs.ASSETTAG, cs.CALNUM, es.CHANGEBY, es.CHANGEDATE FROM cs INNER JOIN es ON cs.ASSETNUM=es.ASSETNUM;"); JavaRDD<String> result = fs.map(new Function<Row, String>() { public String call(Row row) { return row.getString(0); } }); result.saveAsTextFile("hdfs://path/to/hdfs/dir-name"); //instead of hdfs I need to save it on mysql database, but I am not able to find any Spark-MYSQL connection } } Here at the end I am saving the result successfully in HDFS. But now I want to save into MYSQL database. Kindly help me out. Thanks
There are two approaches you can use for writing your results back to the database. One is to use something like DBOutputFormat and configure that, and the other is to use foreachPartition on the RDD you want to save and pass in a function which creates a connection to MySQL and writes the result back.
Here is an example using DBOutputFormat. Create a class that represents your table row - public class TableRow implements DBWritable { public String column1; public String column2; #Override public void write(PreparedStatement statement) throws SQLException { statement.setString(1, column1); statement.setString(2, column2); } #Override public void readFields(ResultSet resultSet) throws SQLException { throw new RuntimeException("readFields not implemented"); } } Then configure your job and write a mapToPair function. The value doesn't appear to be used. If anyone knows, please post a comment. String tableName = "YourTableName"; String[] fields = new String[] { "column1", "column2" }; JobConf job = new JobConf(); DBConfiguration.configureDB(job, "com.mysql.jdbc.Driver", "jdbc:mysql://localhost/DatabaseNameHere", "username", "password"); DBOutputFormat.setOutput(job, tableName, fields); // map your rdd into a table row JavaPairRDD<TableRow, Object> rows = rdd.mapToPair(...); rows.saveAsHadoopDataset(job);
Grid in Struts2 using struts2-jquery-grid plugin
I'm trying with a Struts jQuery grid using the struts2-jquery-grid-3.7.0 plugin as demonstrated on the showcase, Grid (Editable/Multiselect). The Struts form: <s:form namespace="/admin_side" action="Test" validate="true" id="dataForm" name="dataForm"> <s:url id="remoteurl" action="TestGrid" namespace="/admin_side"/> <s:url id="editurl" action="edit-grid-entry"/> <sjg:grid id="gridmultitable" caption="Example (Editable/Multiselect)" dataType="json" href="%{remoteurl}" pager="true" navigator="true" navigatorSearchOptions="{sopt:['eq','ne','lt','gt']}" navigatorAddOptions="{height:280, width:500, reloadAfterSubmit:true}" navigatorEditOptions="{height:280, width:500, reloadAfterSubmit:false}" navigatorEdit="true" navigatorView="true" navigatorViewOptions="{height:280, width:500}" navigatorDelete="true" navigatorDeleteOptions="{height:280, width:500,reloadAfterSubmit:true}" gridModel="gridModel" rowList="5,10,15" rowNum="5" rownumbers="true" editurl="%{editurl}" editinline="true" multiselect="true" onSelectRowTopics="rowselect" > <sjg:gridColumn name="countryId" index="countryId" title="Id" formatter="integer" editable="false" sortable="true" search="true" sorttype="integer" searchoptions="{sopt:['eq','ne','lt','gt']}"/> <sjg:gridColumn name="countryName" index="countryName" title="Country Name" editable="true" edittype="text" sortable="true" search="true" sorttype="text"/> <sjg:gridColumn name="countryCode" index="countryCode" title="Country Code" sortable="true" search="true" editable="true" sorttype="text"/> </sjg:grid> </s:form> The action class: #Namespace("/admin_side") #ResultPath("/WEB-INF/content") #ParentPackage(value = "json-default") #InterceptorRefs(#InterceptorRef(value = "store", params = {"operationMode", "AUTOMATIC"})) public final class TestAction extends ActionSupport implements Serializable { #Autowired private final transient CountryService countryService=null; private static final long serialVersionUID = 1L; // Result List private List<Country> gridModel; // Get how many rows we want to have into the grid - rowNum attribute in the grid private Integer rows=5; // Get the requested page. By default grid sets this to 1. private Integer page=1; // sorting order - asc or desc private String sord; // get index row - i.e. user click to sort. private String sidx; // Search Field private String searchField; // The Search String private String searchString; // The Search Operation ['eq','ne','lt','le','gt','ge','bw','bn','in','ni','ew','en','cn','nc'] private String searchOper; // Your Total Pages private Integer total; // All Records private Integer records; #Action(value = "TestGrid", results = { #Result(name = ActionSupport.SUCCESS, type = "json", params = {"includeProperties", "gridModel\\[\\d+\\]\\.countryId, gridModel\\[\\d+\\]\\.countryName, gridModel\\[\\d+\\]\\.countryCode", "excludeNullProperties", "true"})}, interceptorRefs = { #InterceptorRef(value = "json")}) public String execute() { records=countryService.rowCount().intValue(); total=new BigDecimal(records).divide(new BigDecimal(rows), 0, BigDecimal.ROUND_CEILING).intValue(); gridModel=countryService.getList(page, rows, null, null); System.out.println("records "+records+" total "+total+" Page " + getPage() + " Rows " + getRows() + " Sort Order " + getSord() + " Index Row :" + getSidx()+"Search :" + searchField + " " + searchOper + " " + searchString); return SUCCESS; } public String getJSON() { return execute(); } public Integer getRows() { return rows; } public void setRows(Integer rows) { this.rows = rows; } public Integer getPage() { return page; } public void setPage(Integer page) { this.page = page; } public Integer getTotal() { return total; } public void setTotal(Integer total) { this.total = total; } public Integer getRecords() { return records; } public void setRecords(Integer records) { this.records = records; } public List<Country> getGridModel() { return gridModel; } public void setGridModel(List<Country> gridModel) { this.gridModel = gridModel; } public String getSord() { return sord; } public void setSord(String sord) { this.sord = sord; } public String getSidx() { return sidx; } public void setSidx(String sidx) { this.sidx = sidx; } public void setSearchField(String searchField) { this.searchField = searchField; } public void setSearchString(String searchString) { this.searchString = searchString; } public void setSearchOper(String searchOper) { this.searchOper = searchOper; } #Action(value = "Test", results = { #Result(name = ActionSupport.SUCCESS, location = "Test.jsp"), #Result(name = ActionSupport.INPUT, location = "Test.jsp")}, interceptorRefs = { #InterceptorRef(value = "defaultStack", params = {"validation.validateAnnotatedMethodOnly", "true", "validation.excludeMethods", "load"})}) public String load() throws Exception { return ActionSupport.SUCCESS; } } The next and previous pagination links are always disabled as shown in the following snap shot. It indicates, page 1 of 1. There are no more pages. Hence, the links are disabled. In the execute() method, however, records is initialized to 31, total to 7 Page to 1 Rows to 5. All other properties are always null whether or not links/buttons for sorting, searching, editing, adding a row are clicked. What am I overlooking here?
You forgot to include fields to json result that are necessary for grid to function properly. Change to #Result(type = "json", params = {"includeProperties", "gridModel\\[\\d+\\]\\.countryId, gridModel\\[\\d+\\]\\.countryName, gridModel\\[\\d+\\]\\.countryCode, total, records, rows, page, sord, sidx, searchField, searchString, searchOper", "excludeNullProperties", "true"}) Also, json interceptor is not necessary, but params is required. #InterceptorRef("params")