【问题标题】:Batch processing files and diff with database批处理文件和与数据库的差异
【发布时间】:2018-10-03 13:01:35
【问题描述】:

目前我正在开发一个 Spring-Boot 应用程序,该应用程序定期尝试处理包含用户数据的文件,其中每行包含 userIddepartamentId,由 | 分隔,例如 123534|13。该文件将包含几百万条记录。

我的要求是以这种方式将这些数据加载到mysql数据库中:

  • 如果存在已处理 ID 的用户,不要做任何事情
  • 如果用户不存在创建新用户
  • 如果用户不在列表中但存在于数据库中,将其删除
  • 如果当前部门不在数据库中,创建它

我做了一些优化

  • 缓存部门以填充实体
  • 批量收集用户保存并通过JpaRepositorysaveAll方法保存

但是我仍然对数据库进行了太多的数据库调用,我正在检查用户是否存在以便为每条记录创建实体以保存...

我的实体相当简单:

@Entity
@Table(name = "departaments")
public class Departament{

    @Id
    @Column(name = "id")
    private Long id;

    @Column(name = "name")
    private String name;

和:

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    @ManyToOne
    @JoinColumn(name = "departament_id")
    private Departament departament;

有人遇到过这样的问题吗?

能不能再优化一下?

有什么好的处理模式吗?

【问题讨论】:

    标签: mysql spring-boot batch-processing


    【解决方案1】:

    这里有几件事:

    1. 您对用户的主要信息来源似乎是 CSV 文件。为什么不简单地截断并重新创建USER 表?您可能会遇到一些问题(我知道引用完整性在您的场景中不是其中之一 - 或者是吗?),但您将免费删除用户(TBH 我不能完全想象一下您在当前设置中如何处理用户删除)。它会运行得更快with key checks disabled
    2. 在使用saveAll 时,您是否真的看到了性能改进?这并不限制要执行的SELECT 语句的数量
    3. 您确定您在正确的抽象级别上运行吗?也许您可以使用纯 JDBC 而不是 JPA。使用 JPA,将涉及大量缓存/映射,从而导致大量开销。使用 JDBC,您可以利用 MySQL 的 INSERT IGNOREINSERT ... ON DUPLICATE KEY UPDATE 语句来获得所需的内容
    4. 如果您选择上述任何一种方法,您可以尝试使用Spring Batch 进行更多声明性处理

    【讨论】:

      【解决方案2】:

      如果是“更换”,请这样做以避免任何停机:

      CREATE TABLE new LIKE old;
      LOAD DATA INFILE ... (and any other massaging)
      RENAME TABLE real TO old, new TO real;
      DROP TABLE old;
      

      如果是“delta”,则将LOAD它放到一个单独的表中,然后执行合适的SQL语句来执行更新。您的问题中的每个项目符号项大约是一个 SQL 语句。没有循环。

      【讨论】:

        【解决方案3】:

        尝试简单地使用 Spring-batch 来完成

        context.xml

        <beans xmlns="http://www.springframework.org/schema/beans"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="
                http://www.springframework.org/schema/beans 
                http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
        
            <!-- stored job-meta in database -->
            <bean id="jobRepository"
                class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean">
                <property name="dataSource" ref="dataSource" />
                <property name="transactionManager" ref="transactionManager" />
                <property name="databaseType" value="mysql" />
            </bean>
        
            <bean id="jobLauncher"
                class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
                <property name="jobRepository" ref="jobRepository" />
            </bean>
        
        </beans>
        

        database.xml

        <beans xmlns="http://www.springframework.org/schema/beans"
            xmlns:jdbc="http://www.springframework.org/schema/jdbc" 
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://www.springframework.org/schema/beans 
                http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                http://www.springframework.org/schema/jdbc 
                http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd">
        
            <!-- connect to database -->
            <bean id="dataSource"
                class="org.springframework.jdbc.datasource.DriverManagerDataSource">
                <property name="driverClassName" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://localhost:3306/test" />
                <property name="username" value="root" />
                <property name="password" value="User@1234" />
            </bean>
        
            <bean id="transactionManager"
                class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />
        
            <!-- create job-meta tables automatically -->
            <jdbc:initialize-database data-source="dataSource">
                <jdbc:script location="org/springframework/batch/core/schema-drop-mysql.sql" />
                <jdbc:script location="org/springframework/batch/core/schema-mysql.sql" />
            </jdbc:initialize-database>
        
        </beans>
        

        jobReport.xml

        <beans xmlns="http://www.springframework.org/schema/beans"
            xmlns:batch="http://www.springframework.org/schema/batch" 
            xmlns:task="http://www.springframework.org/schema/task"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://www.springframework.org/schema/batch
                http://www.springframework.org/schema/batch/spring-batch-2.2.xsd
                http://www.springframework.org/schema/beans 
                http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
        
            <bean id="report" class="com.om.model.Report" scope="prototype" />
        
            <batch:job id="reportJob">
                <batch:step id="step1">
                    <batch:tasklet>
                        <batch:chunk reader="cvsFileItemReader" writer="mysqlItemWriter"
                            commit-interval="2">
                        </batch:chunk>
                    </batch:tasklet>
                </batch:step>
            </batch:job>
        
            <bean id="cvsFileItemReader" class="org.springframework.batch.item.file.FlatFileItemReader">
        
                <!-- Read a csv file -->
                <property name="resource" value="classpath:cvs/report.csv" />
        
                <property name="lineMapper">
                    <bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
        
                        <!-- split it -->
                        <property name="lineTokenizer">
                            <bean
                                class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
                                <property name="names" value="userId,departmentId" />
                            </bean>
                        </property>
        
                        <property name="fieldSetMapper">
        
                            <!-- return back to reader, rather than a mapped object. -->
                            <!--
                                <bean class="org.springframework.batch.item.file.mapping.PassThroughFieldSetMapper" />
                            -->
        
                            <!-- map to an object -->
                            <bean
                                class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper">
                                <property name="prototypeBeanName" value="report" />
                            </bean>
        
                        </property>
        
                    </bean>
                </property>
        
            </bean>
        
            <bean id="mysqlItemWriter"
                class="org.springframework.batch.item.database.JdbcBatchItemWriter">
                <property name="dataSource" ref="dataSource" />
                <property name="sql">
                    <value>
                    <![CDATA[        
                        insert into RAW_REPORT(userId,departmentId) values (:userId, :departmentId)
                    ]]>
                    </value>
                </property>
                <!-- It will take care matching between object property and sql name parameter -->
                <property name="itemSqlParameterSourceProvider">
                    <bean
                        class="org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider" />
                </property>
            </bean>
        
        </beans>
        

        App.java它是你的工作执行的主要类

        package com.om;
        
        import org.springframework.batch.core.Job;
        import org.springframework.batch.core.JobExecution;
        import org.springframework.batch.core.JobParameters;
        import org.springframework.batch.core.launch.JobLauncher;
        import org.springframework.context.ApplicationContext;
        import org.springframework.context.support.ClassPathXmlApplicationContext;
        
        public class App {
            public static void main(String[] args) throws IllegalStateException {
        
                String[] springConfig  = 
                    {   "spring/batch/config/database.xml", 
                        "spring/batch/config/context.xml",
                        "spring/batch/jobs/job-report.xml" 
                    };
        
                ApplicationContext context = 
                        new ClassPathXmlApplicationContext(springConfig);
        
                JobLauncher jobLauncher = (JobLauncher) context.getBean("jobLauncher");
                Job job = (Job) context.getBean("reportJob");
        
                try {
        
                    JobExecution execution = jobLauncher.run(job, new JobParameters());
                    System.out.println("Exit Status : " + execution.getStatus());
        
                } catch (Exception e) {
                    e.printStackTrace();
                }
        
                System.out.println("Done");
        
            }
        }
        

        Report.java 这是你的 Pojo

        package com.om.model;
        
        public class Report {
        
            private String userId;
            private String departmentId;
            public String getUserId() {
                return userId;
            }
            public void setUserId(String userId) {
                this.userId = userId;
            }
            public String getDepartmentId() {
                return departmentId;
            }
            public void setDepartmentId(String departmentId) {
                this.departmentId = departmentId;
            }
        
        
        }
        

        现在您需要将您的 report.csv 放入包含数百万个 userId 和 departmentId 的资源文件夹中。

        您可以简单地查看您的数据库表如何自动执行作业以及数据库条目。 请寻求任何需要的帮助。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2010-09-06
          • 2020-11-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多