The problem

In the current program design, it is common to see a single entity class corresponding to multiple tables in the database (of course, the same is true for the fields of the tables in the database). Hibernate framework can handle a single entity class corresponding to a single database table without any problems. But the question arises, how does a single entity class correspond to multiple database tables?

The desired result

@Entity
@Table(name = "clues")
public class CluesDto {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "clues_name") private String cluesName; / /... A set of setter/getter methods} now want to map this entity class to the database and have it automatically generated by month: clues_01,clues_02,clues_03...... Clues_12 (there might be generated by day 366 tables), though, you can write a script, but if it is a project of the nature of things, need to deploy to different types of database (mysql, oracle, db2, postgresql, sysbase, etc.), modify the script very troublesome.Copy the code

The solution

Tracing Hibernate source code

Tracking Hibernate create table structure of the source code, to finally found org. Hibernate. Tool. Schema. Internal. SchemaCreatorImpl this class is responsible for all the database initialization, The key is this method on line 103doCreation(
		Metadata metadata,
		ExecutionOptions options,
		SourceDescriptor sourceDescriptor,
		TargetDescriptor targetDescriptor) {
	if ( targetDescriptor.getTargetTypes().isEmpty() ) {
		return;
	}

	final JdbcContext jdbcContext = tool.resolveJdbcContext( options.getConfigurationValues() );
	final GenerationTarget[] targets = tool.buildGenerationTargets(
			targetDescriptor,
			jdbcContext,
			options.getConfigurationValues(),
			true
	);

	doCreation( metadata, jdbcContext.getDialect(), options, sourceDescriptor, targets ); SQL > alter table Metadata; SQL > alter table Metadata; SQL > alter table Metadata; SQL > alter table Metadata; It is possible for a single entity class to create multiple database tables. Following this idea, let's look at how to modify Metadata. In the org. Hibernate. Internal. SessionFactoryImpl constructor of a class (don't know why the constructor to write so many logic, also not split between several method), in 285 rowsfor( Integrator integrator : serviceRegistry.getService( IntegratorService.class ).getIntegrators() ) { integrator.integrate( metadata, this, this.serviceRegistry ); integratorObserver.integrators.add( integrator ); } check Integrator interface, see is in: org. Hibernate. Integrator. The spi package below, you can use Java technology of spi, through reflection technology after get the Metadata Tables of Map object, then can operation experimentCopy the code

Integrator interface implementation (in the form of SPI mechanism, similar to Java’s database-driven writing)

public class MultipleTableIntegrator implements Integrator {
    private static final Logger logger = LoggerFactory.getLogger(MultipleTableIntegrator.class);
    private static final String MONTH_DISTRIBUTION_TABLE_NAME_CLUES = "clues";

    @Override
    public void integrate(Metadata metadata, SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
        logger.info("You can manipulate this interface via SPI."); Optional.ofNullable(metadata) .map(Metadata::getDatabase) .map(Database::getNamespaces) .ifPresent(namespaces -> Namespaces. ForEach (namespace -> {try {// the tables attribute of a namespace can be obtained by reflection. Field fieldTables = namespace.getClass().getDeclaredField()"tables");
                        fieldTables.setAccessible(true);
                        Object value = fieldTables.get(namespace);
                        if (value instanceof Map) {
                            Object cluesTable = ((Map) value).get(Identifier.toIdentifier(MONTH_DISTRIBUTION_TABLE_NAME_CLUES));
                            IntStream.rangeClosed(1, 12)
                                    .boxed()
                                    .forEach(month -> {
                                        String name = String.join("_", MONTH_DISTRIBUTION_TABLE_NAME_CLUES, month.toString());
                                        Identifier key = Identifier.toIdentifier(name);
                                        ((Map) value).put(key, cloneTable((Table) cluesTable, name));
                                    });
                        }
                    } catch (NoSuchFieldException | IllegalAccessException e) {
                        logger.info("Reflection error..."); logger.error(e.getMessage(), e); }})); } @Override public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) { logger.info("You can do this via SPI...."); } // if there is a foreign key identifier, please manually modify the private TablecloneTable(Table source, String identifierName) {
        Table target = new Table();
        BeanUtils.copyProperties(source, target);
        target.setName(identifierName);
        source.getColumnIterator().forEachRemaining(o -> {
            target.addColumn((Column) o);
        });
        returntarget; }}Copy the code

Configure meta-INF information

Under the resource, the newly built meta-inf folder, and then build services folder, and then create: org. Hibernate. Integrator. Spi. The integrator for the file name of the file, Contents include: org. Lx. Bird. Framework. Springboot. Client. Spi. MultipleTableIntegrator (this is a local implementation of the full path) you must remember in pom. The XML filter modify packaging resources, Modify the filter file rules in the resources filter link, otherwise the above files can not be packaged into the Java SPI mechanism, you can search on the InternetCopy the code

The latter

In this exploration, I realized the power of bytecode technology. With bytecode technology, which means that javaAgent starts with this parameter, you can modify the source code as you like. When loading the class file, you can modify the call to the tables and manually add it directly. I haven’t tried it yet. I’ve tried it enough to add a line of code to a certain number of lines!