Code Bye

java Web如何离线使用并进行数据同步

最近做了一个物业管理系统,Boss希望可以在离线的时候进行操作,让我想下解决方案。

我只想到一种最笨的,在服务器和客户本机分别部署两套系统,在断网的时候操作本地,在联网的时候操作远程并同步双方数据库里的数据,同步的方式是在表里添加同步标示(使用定时器同步)。

今天粗略看了下,如果按照我的这个方案做,我们之前完成的模块有差不多需要20多长表需要改动。工作量比较大了。

所以问下有没有哪位仁兄做过此方面的内容,望解答!

只能说,当初没想好,导致现在的维护扩展困难,所以节哀吧,根据实际情况去改吧,,,都是半夜统计前一天的数据,然后第二天查看前一天的数据,如果实时性要求高的统计,那是不允许 这样做咯
你是WEB系统不是桌面系统,WEB系统存在离线一说么。你难道要在每个客服的PC上装一个tomcat和JDK,这科学么。叫你们BOSS换成桌面应用系统可以考滤一下离线。然后用你这套思路来实现。
引用 1 楼 shadowsick 的回复:

只能说,当初没想好,导致现在的维护扩展困难,所以节哀吧,根据实际情况去改吧,,,都是半夜统计前一天的数据,然后第二天查看前一天的数据,如果实时性要求高的统计,那是不允许 这样做咯

如果要把Tomcat部署到客户本机的话,我想一小时从服务器抓取一次数据比较合适吧?隔一天是不是有点久了 。。

引用 2 楼 bichir 的回复:

你是WEB系统不是桌面系统,WEB系统存在离线一说么。你难道要在每个客服的PC上装一个tomcat和JDK,这科学么。叫你们BOSS换成桌面应用系统可以考滤一下离线。然后用你这套思路来实现。

现在就是有这么一个需求,我们的系统在网上供客户访问,客户搞个需求想在断网的时候也可以进行一些重要的操作,所以只能针对现在项目想些解决方案了。客户端也考虑过,试着用javaFX做了几个模型,发现些问题:1是太难看了,2 是javaFX网上资料太少,周期短的基础上进行深度开发不太现实。。所以又把注意力转到Web上了

引用 3 楼 EthanLindp 的回复:
Quote: 引用 1 楼 shadowsick 的回复:

只能说,当初没想好,导致现在的维护扩展困难,所以节哀吧,根据实际情况去改吧,,,都是半夜统计前一天的数据,然后第二天查看前一天的数据,如果实时性要求高的统计,那是不允许 这样做咯

如果要把Tomcat部署到客户本机的话,我想一小时从服务器抓取一次数据比较合适吧?隔一天是不是有点久了 。。

这个时间根据你的业务需求看吧,因为我们的数据量有点大,一个小时都跑步完的,而且白天跑的时候对系统存在一定压力,所以一般都是半夜跑,这个请根据你的实际情况分析,呵呵

想多了吧,你见过web还能离线?web的意思就是东西都在服务器上,客户端只有一个浏览器而已,这种怎么离线,赶快停止你老板稀奇的设想,不然你下一步就是要开发一个pc客户端了。

80分
你可以考虑用tomcat mini+derby+jre打包成客户端,然后在主系统做个模块:
1、上传用户安装路径
2、操作离线功能的启动和停止(运行bat或者exe 启动服务)
3、数据同步,数据可以同步一次删除一次旧数据(用户可以手动同步和自动同步数据)
还有是否所有功能都需要离线的问题。

20分
WEB是不能离线的,转移也没用。不过我倒想到一种用C+JS+HTML。用HTML与JS做一个界面,然后用C写一个应用框架,主要功能是读取本地文件与保存文件等,并提供给JS调用的API,用JS调用C来完成文件的存储与修改等,在离线时就可以用这一套。在线时就把本地文件与线上的数据库同步。写好之后也像一个伪桌面应用程序了,直接把客户端发给客户。
apache mina项目提供了http/ftp/xmpp服务,而derby数据库最适合小型的数据存储,你只要把项目部分功能放在mina项目目录中,然后web控制启动就可以达到一个小型服务器的效果了。
引用 5 楼 shadowsick 的回复:
Quote: 引用 3 楼 EthanLindp 的回复:
Quote: 引用 1 楼 shadowsick 的回复:

只能说,当初没想好,导致现在的维护扩展困难,所以节哀吧,根据实际情况去改吧,,,都是半夜统计前一天的数据,然后第二天查看前一天的数据,如果实时性要求高的统计,那是不允许 这样做咯

如果要把Tomcat部署到客户本机的话,我想一小时从服务器抓取一次数据比较合适吧?隔一天是不是有点久了 。。

这个时间根据你的业务需求看吧,因为我们的数据量有点大,一个小时都跑步完的,而且白天跑的时候对系统存在一定压力,所以一般都是半夜跑,这个请根据你的实际情况分析,呵呵

那没办法了,我们情况还比较特殊。如果部署也只是在客户的电脑上安装一个Tomcat配置应用程序。只能白天定时跑,晚上客户就关机回家了 。。 

引用 6 楼 ygycomon 的回复:

想多了吧,你见过web还能离线?web的意思就是东西都在服务器上,客户端只有一个浏览器而已,这种怎么离线,赶快停止你老板稀奇的设想,不然你下一步就是要开发一个pc客户端了。

我也想停止啊,可是停不下来的赶脚~ 只能硬着头皮做了 看能做出个什么东西

引用 7 楼 huasuoworld 的回复:

你可以考虑用tomcat mini+derby+jre打包成客户端,然后在主系统做个模块:
1、上传用户安装路径
2、操作离线功能的启动和停止(运行bat或者exe 启动服务)
3、数据同步,数据可以同步一次删除一次旧数据(用户可以手动同步和自动同步数据)
还有是否所有功能都需要离线的问题。

嗯,你说的这个是需要我把本地版本开发完成后,把WEB打包安装的内容吧?现在考虑还有点早 ~

数据问题你可以做个线程把主数据库的数据抽取部分要用的导入到用户那边(可以客户手动与自动),线程加个判断能否连接用户PC,如果可以则提取数据到主数据库表里,表不需要变啊,copy一份到客户端就行了。
我现在遇到一个问题,就是以目前的需求情况我需要在项目中配置两个数据源(本地、远程)在同步的时候切换,我现在使用Spring的方式,继承AbstractRoutingDataSource类完成在代码上手动切换。
private boolean doBaseCompany() {
		if (!Boolean.valueOf(prop.get("BaseCompany").toString()))
			return true;
		DBContextHolder.setCustomerType(DBContextHolder.DATASOURCE_LOCAL); // 切换数据源到本地
		SysRole role = new SysRole();
		role.setCompanyId("lindp1");
		role.setRoleName("lindp1");
		sysRoleDao.addSysRole(role);

		DBContextHolder.setCustomerType(DBContextHolder.DATASOURCE_REMOTE); // 切换数据源到远程
		SysRole role2 = new SysRole();
		role2.setCompanyId("lindp2");
		role2.setRoleName("lindp2");
		sysRoleDao.addSysRole(role2);
		return true;
	}

就是通过上面代码实现数据库切换,可是我添加的时候定时器第一次执行只会给远程数据库添加两条(lindp1、lindp2)的数据,本地数据库根本没操作。不知道是不是事务的问题?是不是要配置分布式事务啊?

引用 13 楼 huasuoworld 的回复:

数据问题你可以做个线程把主数据库的数据抽取部分要用的导入到用户那边(可以客户手动与自动),线程加个判断能否连接用户PC,如果可以则提取数据到主数据库表里,表不需要变啊,copy一份到客户端就行了。

数据问题我都没有考虑线程,有必要使用线程吗?定时器也可以吧

引用 15 楼 EthanLindp 的回复:
Quote: 引用 13 楼 huasuoworld 的回复:

数据问题你可以做个线程把主数据库的数据抽取部分要用的导入到用户那边(可以客户手动与自动),线程加个判断能否连接用户PC,如果可以则提取数据到主数据库表里,表不需要变啊,copy一份到客户端就行了。

数据问题我都没有考虑线程,有必要使用线程吗?定时器也可以吧

动态获取数据源需要线程

你参考下http://blog.csdn.net/shuai825644975/article/details/17021181 
引用 17 楼 huasuoworld 的回复:

你参考下http://blog.csdn.net/shuai825644975/article/details/17021181 

对,我就是使用这种方式配置的多数据源。我的事务不起作用,家了@Transactional注解后第一次加载数据源无效,使用的是默认数据源 。。

你调用第二个数据源前执行了线程的remove()以及commit事务了吗?
那你用mq啊。
引用 20 楼 fangmingshijie 的回复:

那你用mq啊。

MQ是什么  全称叫啥  我搜搜看

引用 19 楼 huasuoworld 的回复:

你调用第二个数据源前执行了线程的remove()以及commit事务了吗?

我就没用线程,一直在使用定时器。
我在项目中配置了atomikos分布式事务,网上说spring在一个事务中不支持多数据源切换。
我现在就做不下去了 。。

你参考下JBOSS XA数据源
standalone.xml
<datasources>
    <datasource jndi-name="java:jboss/datasources/ExampleDS" pool-name="ExampleDS" enabled="true" use-java-context="true">
        <connection-url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1</connection-url>
        <driver>h2</driver>
        <security>
            <user-name>sa</user-name>
            <password>sa</password>
        </security>
    </datasource>
    <xa-datasource jta="true" jndi-name="java:jboss/datasources/MYDB_ONE" pool-name="MYDB_ONE" enabled="true" use-java-context="true" use-ccm="true">
        <xa-datasource-property name="ServerName">
            localhost
        </xa-datasource-property>
        <xa-datasource-property name="DatabaseName">
            MYDB_ONE
        </xa-datasource-property>
        <xa-datasource-property name="SelectMethod">
            cursor
        </xa-datasource-property>
        <xa-datasource-class>com.microsoft.sqlserver.jdbc.SQLServerXADataSource</xa-datasource-class>
        <driver>sqljdbc</driver>
        <xa-pool>
            <is-same-rm-override>false</is-same-rm-override>
        </xa-pool>
        <security>
            <user-name>some_user</user-name>
            <password>some_password</password>
        </security>
        <validation>
            <valid-connection-checker class-name="org.jboss.jca.adapters.jdbc.extensions.mssql.MSSQLValidConnectionChecker"/>
        </validation>
    </xa-datasource>
    <xa-datasource jta="true" jndi-name="java:jboss/datasources/MYDB_TWO" pool-name="MYDB_TWO" enabled="true" use-java-context="true" use-ccm="true">
        <xa-datasource-property name="ServerName">
            localhost
        </xa-datasource-property>
        <xa-datasource-property name="DatabaseName">
            MYDB_TWO
        </xa-datasource-property>
        <xa-datasource-property name="SelectMethod">
            cursor
        </xa-datasource-property>
        <xa-datasource-class>com.microsoft.sqlserver.jdbc.SQLServerXADataSource</xa-datasource-class>
        <driver>sqljdbc</driver>
        <xa-pool>
            <is-same-rm-override>false</is-same-rm-override>
        </xa-pool>
        <security>
            <user-name>some_user</user-name>
            <password>some_password</password>
        </security>
        <validation>
            <valid-connection-checker class-name="org.jboss.jca.adapters.jdbc.extensions.mssql.MSSQLValidConnectionChecker"/>
        </validation>
    </xa-datasource>
    <drivers>
        <driver name="h2" module="com.h2database.h2">
            <xa-datasource-class>org.h2.jdbcx.JdbcDataSource</xa-datasource-class>
        </driver>
        <driver name="sqljdbc" module="com.microsoft.sqlserver.jdbc">
            <driver-class>com.microsoft.sqlserver.jdbc.SQLServerDriver</driver-class>
        </driver>
        <driver name="postgresql" module="org.postgresql">
            <xa-datasource-class>org.postgresql.xa.PGXADataSource</xa-datasource-class>
        </driver>
    </drivers>
</datasources>

springJpaConfig.xml

<!-- Use @PersistenceContext annotations for injecting entity managers -->
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />

<!-- Set up JTA transaction manager -->
<tx:jta-transaction-manager />

<bean id="entityManagerFactoryMyDB" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="persistenceUnitName" value="MyDB" />
    <property name="dataSource" ref="dataSourceMyDB" />
    <property name="packagesToScan" value="my.package.with.jpa.entities" />
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
            <property name="showSql" value="true" />
        </bean>
    </property>
    <property name="jpaPropertyMap">
        <map>
            <entry key="javax.persistence.transactionType" value="jta" />

            <entry key="hibernate.transaction.jta.platform" value="org.hibernate.service.jta.platform.internal.JBossAppServerJtaPlatform" />
            <entry key="jboss.entity.manager.factory.jndi.name" value="java:app/MyDBEntityManagerFactory" />

            <entry key="hibernate.dialect" value="org.hibernate.dialect.SQLServer2008Dialect" />
        </map>
    </property>
</bean>

<bean id="dataSourceMyDB" class="some.package.AbstractRoutingDataSourceMyDB">
    <property name="lenientFallback" value="false" />
    <property name="defaultTargetDataSource" value="java:jboss/datasources/ExampleDS" />
    <property name="targetDataSources">
        <map key-type="String">
            <!-- This is a placeholder that will be filled in by BeanFactoryPostProcessor -->
        </map>
    </property>
</bean>

<!-- This allows us to modify Spring configuration load the list of datasources -->
<bean class="some.package.DatasourceRegisteringBeanFactoryPostProcessor" />

DatasourceRegisteringBeanFactoryPostProcessor.java

package some.package
class DatasourceRegisteringBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        HashMap<String, String> connectionsListMyDB = new HashMap<>();

        // Load your connection list from wherever you need to, you can
        // enumerate them directly from JNDI or some configuration location
        connectionsListMyDB.put("db1", "java:jboss/datasources/MYDB_ONE");
        connectionsListMyDB.put("db2", "java:jboss/datasources/MYDB_TWO");

        if (connectionsList.isEmpty())
            throw new RuntimeException("No JPA connections defined");

        // Configure the dataSource bean properties
        BeanDefinitionRegistry factory = (BeanDefinitionRegistry) beanFactory;
        MutablePropertyValues mpv = factory.getBeanDefinition("dataSourceMyDB").getPropertyValues();

        ManagedMap<String, String> mm = (ManagedMap<String, String>) mpv.getPropertyValue(
                "targetDataSources").getValue();
        mm.clear();
        for (Entry<String, String> e : connectionsListMyDB.entrySet()) {
            mm.put(e.getKey(), e.getValue());
        }
    }
}

AbstractRoutingDataSourceMyDB.java

public class AbstractRoutingDataSourceMyDB extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return getDbConnectionMyDB();
    }

    // ThreadLocal variable so that the connection gets set for the current thread
    // using spring""s request scope on the class instead of ThreadLocal would also work here.
    private final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

    public void setDbConnectionMyDB(String myKey) {
        Assert.notNull(myKey, "myKey cannot be null");

        contextHolder.set(myKey);

        String k = contextHolder.get();
    }

    public String getDbConnectionMyDB() {
        return (String) contextHolder.get();
    }

    public void clearDbConnectionMyDB() {
        contextHolder.remove();
    }
}

SomeTableDAO.java

@PersistenceContext(unitName = "MyDB")
private EntityManager em;

@Autowired
private AbstractRoutingDataSourceMyDB routingSource;

public void someMethod(int id) {
    em.flush();
    em.clear();
    routingSource.setDbConnectionMyDB("db1");
    em.remove(em.getReference(Something.class, id)); // delete something in db1

    em.flush();
    em.clear();
    routingSource.setDbConnectionMyDB("db2");
    em.remove(em.getReference(Something.class, id)); // delete something else with the same id in db2
}

CodeBye 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权 , 转载请注明java Web如何离线使用并进行数据同步