本文介绍了MultiTenancy与Hibernate 4.0的独立模式方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用EJB 3.0和Hibernate 4和PostgreSQL作为我的数据库服务器来创建一个 multitenant 系统,其中每个租户都有独立但相同的架构。我仍处于试验阶段,我有3个方案 public company1 company2 全部拥有一个表格人员。现在我想要做的是根据用户在运行时更改架构,以便他只能查看他/她的公司的数据。



这里是我的示例代码:
实体对象:

  package com.neebal.domain; 

import java.io.Serializable;
import java.lang.Long;
import java.lang.String;

import javax.persistence。*;
导入org.eclipse.persistence.annotations.Multitenant;
import org.eclipse.persistence.annotations.MultitenantType;

@Entity

// @ Table(schema =company1)
public class Person实现Serializable {


@Id
私人长ID;
私人字符串名称;
private static final long serialVersionUID = 1L;

public Person(){
super();
}
public Long getId(){
return this.id;
}

public void setId(Long id){
this.id = id;
}
public String getName(){
return this.name;
}

public void setName(String name){
this.name = name;




$ b $ MultiTenantConnectionProvider class:


  import java.sql.Connection; 
import java.sql.SQLException;
import java.util.Map;

import org.hibernate.HibernateException;
import org.hibernate.service.config.spi.ConfigurationService;
import org.hibernate.service.jdbc.connections.internal.C3P0ConnectionProvider;
导入org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.hibernate.service.spi.ServiceRegistryAwareService;
import org.hibernate.service.spi.ServiceRegistryImplementor;

公共类MultiTenantProvider实现MultiTenantConnectionProvider,ServiceRegistryAwareService {

private static final long serialVersionUID = 4368575201221677384L;

private C3P0ConnectionProvider connectionProvider = null;

@Override
public boolean supportsAggressiveRelease(){
return false;

$ b @Override
public void injectServices(ServiceRegistryImplementor serviceRegistry){
Map lSettings = serviceRegistry.getService(ConfigurationService.class).getSettings();

connectionProvider = new C3P0ConnectionProvider();
connectionProvider.injectServices(serviceRegistry);
connectionProvider.configure(lSettings);
}

@Override
public boolean isUnwrappableAs(Class clazz){
return false;
}

@Override
public< T> T unwrap(Class< T> clazz){
return null;

$ b @Override
public Connection getAnyConnection()throws SQLException {
final Connection connection = connectionProvider.getConnection();
返回连接;

$ b @Override
public Connection getConnection(String tenantIdentifier)throws SQLException {
final Connection connection = getAnyConnection();
try {
connection.createStatement()。execute(SET SCHEMA'+ tenantIdentifier +');
}
catch(SQLException e){
抛出新的HibernateException(无法将JDBC连接更改为指定模式[+ tenantIdentifier +],e);
}
返回连接;

$ b @Override
public void releaseAnyConnection(Connection connection)throws SQLException {
try {
connection.createStatement()。execute(SET SCHEMA '上市');
}
catch(SQLException e){
抛出新的HibernateException(无法将JDBC连接更改为指定模式[public],e);
}
connectionProvider.closeConnection(connection);

$ b @Override
public void releaseConnection(String tenantIdentifier,Connection connection)throws SQLException {
releaseAnyConnection(connection);




$ b $ CurrentTenantIdentifierResolver class:

  import org.hibernate.context.spi.CurrentTenantIdentifierResolver; 

public class SchemaResolver implements CurrentTenantIdentifierResolver {
$ b $ @Override
public String resolveCurrentTenantIdentifier(){
System.out.println(company1);
返回company1; // TODO:实现服务以识别租户,如:userService.getCurrentlyAuthUser()。getTenantId();
}

@Override
public boolean validateExistingCurrentSessions(){
return false;




$ b $ persistence.xml file:

 <?xml version =1.0encoding =UTF-8?> ; 
< persistence version =2.0
xmlns =http://java.sun.com/xml/ns/persistencexmlns:xsi =http://www.w3.org/ 2001 / XMLSchema-instance
xsi:schemaLocation =http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd >
< persistence-unit name =testEJB>
< jta-data-source> jdbc / testpgsql< / jta-data-source>
<属性>
< property name =javax.persistence.providervalue =org.hibernate.ejb.HibernatePersistence/>

< property name =hibernate.connection.usernamevalue =postgres/>
< property name =hibernate.connection.passwordvalue =root/>
< property name =hibernate.connection.urlvalue =jdbc:postgresql:// localhost:5432 / test/>
< property name =hibernate.connection.driver_classvalue =org.postgresql.Driver/>

< property name =hibernate.multiTenancyvalue =SCHEMA/>
< property name =hibernate.tenant_identifier_resolvervalue =com.neebal.util.multitenancy.SchemaResolver/>
< property name =hibernate.multi_tenant_connection_provider
value =com.neebal.util.multitenancy.MultiTenantProvider/>

< property name =hibernate.hbm2ddl.autovalue =create-drop/>
< / properties>
< / persistence-unit>
< /余辉>

最后, DAO class: p>

  import java.util.List; 

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import com.neebal.domain.Person;
$ b $ **
* Session Bean实现类PersonDAO
* /
@Stateless
公共类PersonDAO实现PersonDAOLocal {

@PersistenceContext
EntityManager entityManager;
/ **
*默认构造函数。
* /
public PersonDAO(){
// TODO自动生成的构造函数存根
}

@Override
public void save Person person){
entityManager.persist(person);
}

@Override
public List< Person> getAll(){

Person person = entityManager.find(Person.class,2L);
System.out.println(person.getName());
返回null;
}

}

在这个例子中,我硬编码了架构为 company1 但它仍然存在或从公共架构中检索数据。所以我在这个例子中错了什么地方。

解决方案

问题已经有1年了,但我认为使用不同的问题取决于某些运行时情况的模式很常见,所以我会回答。如果我理解你是正确的,并且租户集合很小,那么我认为最简单的方法就是在你的persistence.xml中为每个租户定义单独的持久性单元。

 < persistence-unit name =public> 
..第一个架构的设置
< / persistence-unit>
< persistence-unit name =company1>
..第一个架构的设置
< / persistence-unit>
< persistence-unit name =company2>
..第一个架构的设置
< / persistence-unit>

然后为每一个单独的entityManager:

  @PersistenceContext(unitName =public)
private EntityManager emPublic;

@PersistenceContext(unitName =company1)
私人EntityManager emComp1;

@PersistenceContext(unitName =company2)
private EntityManager emComp1;

现在您可以在实体管理器之间进行切换,给定当前授权用户。



根据您的确切基础设施等,也可能有其他方法。例如,如果所有的模式都在同一台服务器上,那么您也可以尝试将模式名称直接传递给您的查询。



这是纯粹的JPA,因此便携式而不依赖于任何像hibernate这样的持久化提供者,也不取决于你的DBMS。


I am using EJB 3.0 and Hibernate 4 with PostgreSQL as my database server to create a multitenant system where each tenant will have separate but identical schema. I am still in the trial stage where I have 3 schemes public, company1, company2 all having a single table person. Now what i want to do is change the schema in runtime as per the user so that he can view the data of his/her company only.

Here is my sample code:Entity Object:

    package com.neebal.domain;

        import java.io.Serializable;
        import java.lang.Long;
        import java.lang.String;

        import javax.persistence.*;
        import org.eclipse.persistence.annotations.Multitenant;
        import org.eclipse.persistence.annotations.MultitenantType;

        @Entity

        //@Table(schema = "company1")
        public class Person implements Serializable {


    @Id
    private Long id;
    private String name;
    private static final long serialVersionUID = 1L;

    public Person() {
        super();
    }   
    public Long getId() {
        return this.id;
    }

    public void setId(Long id) {
        this.id = id;
    }   
    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }  
}

The MultiTenantConnectionProvider class:

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;

import org.hibernate.HibernateException;
import org.hibernate.service.config.spi.ConfigurationService;
import org.hibernate.service.jdbc.connections.internal.C3P0ConnectionProvider;
import org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.hibernate.service.spi.ServiceRegistryAwareService;
import org.hibernate.service.spi.ServiceRegistryImplementor;

public class MultiTenantProvider implements MultiTenantConnectionProvider, ServiceRegistryAwareService  {

    private static final long serialVersionUID = 4368575201221677384L;

    private C3P0ConnectionProvider connectionProvider = null;

    @Override
    public boolean supportsAggressiveRelease() {
        return false;
    }

    @Override
    public void injectServices(ServiceRegistryImplementor serviceRegistry) {
        Map lSettings = serviceRegistry.getService(ConfigurationService.class).getSettings();

        connectionProvider = new C3P0ConnectionProvider();
        connectionProvider.injectServices(serviceRegistry);
        connectionProvider.configure(lSettings);
    }

    @Override
    public boolean isUnwrappableAs(Class clazz) {
        return false;
    }

    @Override
    public <T> T unwrap(Class<T> clazz) {
        return null;
    }

    @Override
    public Connection getAnyConnection() throws SQLException {
        final Connection connection = connectionProvider.getConnection();
        return connection;
    }

    @Override
    public Connection getConnection(String tenantIdentifier) throws SQLException {
        final Connection connection = getAnyConnection();
        try {
            connection.createStatement().execute("SET SCHEMA '" + tenantIdentifier + "'");
        }
        catch (SQLException e) {
            throw new HibernateException("Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]", e);
        }
        return connection;
    }

    @Override
    public void releaseAnyConnection(Connection connection) throws SQLException {
        try {
            connection.createStatement().execute("SET SCHEMA 'public'");
        }
        catch (SQLException e) {
            throw new HibernateException("Could not alter JDBC connection to specified schema [public]", e);
        }
        connectionProvider.closeConnection(connection);
    }

    @Override
    public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
        releaseAnyConnection(connection);
    }
}

The CurrentTenantIdentifierResolver class:

import org.hibernate.context.spi.CurrentTenantIdentifierResolver;

public class SchemaResolver implements CurrentTenantIdentifierResolver {

    @Override
    public String resolveCurrentTenantIdentifier() {
        System.out.println("company1");
        return "company1"; //TODO: Implement service to identify tenant like: userService.getCurrentlyAuthUser().getTenantId();
    }

    @Override
    public boolean validateExistingCurrentSessions() {
        return false;
    }
}

The persistence.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
    xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
    <persistence-unit name="testEJB">
        <jta-data-source>jdbc/testpgsql</jta-data-source>
        <properties>
            <property name="javax.persistence.provider" value="org.hibernate.ejb.HibernatePersistence" />

            <property name="hibernate.connection.username" value="postgres" />
            <property name="hibernate.connection.password" value="root" />
            <property name="hibernate.connection.url" value="jdbc:postgresql://localhost:5432/test" />
            <property name="hibernate.connection.driver_class" value="org.postgresql.Driver" />

            <property name="hibernate.multiTenancy" value="SCHEMA" />
            <property name="hibernate.tenant_identifier_resolver" value="com.neebal.util.multitenancy.SchemaResolver" />
            <property name="hibernate.multi_tenant_connection_provider"
                value="com.neebal.util.multitenancy.MultiTenantProvider" />

            <property name="hibernate.hbm2ddl.auto" value="create-drop" />
        </properties>
    </persistence-unit>
</persistence>

And finally the DAO class:

import java.util.List;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import com.neebal.domain.Person;

/**
 * Session Bean implementation class PersonDAO
 */
@Stateless
public class PersonDAO implements PersonDAOLocal {

    @PersistenceContext
    EntityManager entityManager;
    /**
     * Default constructor. 
     */
    public PersonDAO() {
        // TODO Auto-generated constructor stub
    }

    @Override
    public void save(Person person) {
        entityManager.persist(person);
    }

    @Override
    public List<Person> getAll() {

        Person person = entityManager.find(Person.class, 2L);
        System.out.println(person.getName());
        return null;
    }

}

In this example I have hardcoded the schema as company1 but it still persists or retrieves the data from public schema. So where am I wrong in this example.

解决方案

The question is already 1 year old, but I think the problem of using different schemas depending on some runtime condition is common one, so I'll answer anyway. If I understand you right and the set of tenants is small, then I think the easiest way to do what you're trying to achieve is to define a separate persistence units for each tenant in your persistence.xml

<persistence-unit name="public">
.. settings for first schema        
</persistence-unit>
<persistence-unit name="company1">
.. settings for first schema        
</persistence-unit>
<persistence-unit name="company2">
.. settings for first schema        
</persistence-unit>

Then have for each one a separate entityManager:

@PersistenceContext(unitName = "public")
private EntityManager emPublic;

@PersistenceContext(unitName = "company1")
private EntityManager emComp1;

@PersistenceContext(unitName = "company2")
private EntityManager emComp1;

Now you can switch between entity managers, given the currently authorized user.

Depending on your exact infrastructure etc, there may be other approaches, too. For instance, if all your schemas are on the same server, then you could also try to pass the schema names directly to your queries.

This is pure JPA and thus portable and not depending on any persistence provider like hibernate nor on your DBMS.

这篇关于MultiTenancy与Hibernate 4.0的独立模式方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-30 06:32