47

I have tried to find information about this but have come up empty handed:

I gather it is possible to create a class dynamically in Java using reflection or proxies but I can't find out how. I'm implementing a simple database framework where I create the SQL queries using reflection. The method gets the object with the database fields as a parameter and creates the query based on that. But it would be very useful if I could also create the object itself dynamically so I wouldn't have the need to have a simple data wrapper object for each table.

The dynamic classes would only need simple fields (String, Integer, Double), e.g.

public class Data {
  public Integer id;
  public String name;
}

Is this possible and how would I do this?

EDIT: This is how I would use this:

/** Creates an SQL query for updating a row's values in the database.
 *
 * @param entity Table name.
 * @param toUpdate Fields and values to update. All of the fields will be
 * updated, so each field must have a meaningful value!
 * @param idFields Fields used to identify the row(s).
 * @param ids Id values for id fields. Values must be in the same order as
 * the fields.
 * @return
 */
@Override
public String updateItem(String entity, Object toUpdate, String[] idFields,
        String[] ids) {
    StringBuilder sb = new StringBuilder();

    sb.append("UPDATE ");
    sb.append(entity);
    sb.append("SET ");

    for (Field f: toUpdate.getClass().getDeclaredFields()) {
        String fieldName = f.getName();
        String value = new String();
        sb.append(fieldName);
        sb.append("=");
        sb.append(formatValue(f));
        sb.append(",");
    }

    /* Remove last comma */
    sb.deleteCharAt(sb.toString().length()-1);

    /* Add where clause */
    sb.append(createWhereClause(idFields, ids));

    return sb.toString();
}
 /** Formats a value for an sql query.
 *
 * This function assumes that the field type is equivalent to the field
 * in the database. In practice this means that this field support two
 * types of fields: string (varchar) and numeric.
 *
 * A string type field will be escaped with single parenthesis (') because
 * SQL databases expect that. Numbers are returned as-is.
 *
 * If the field is null, a string containing "NULL" is returned instead.
 * 
 * @param f The field where the value is.
 * @return Formatted value.
 */
String formatValue(Field f) {
    String retval = null;
    String type = f.getClass().getName();
    if (type.equals("String")) {
        try {
            String value = (String)f.get(f);
            if (value != null) {
                retval = "'" + value + "'";
            } else {
                retval = "NULL";
            }
        } catch (Exception e) {
            System.err.println("No such field: " + e.getMessage());
        }
    } else if (type.equals("Integer")) {
        try {
            Integer value = (Integer)f.get(f);
            if (value != null) {
                retval = String.valueOf(value);
            } else {
                retval = "NULL";
            }
        } catch (Exception e) {
            System.err.println("No such field: " + e.getMessage());
        }
    } else {
        try {
            String value = (String) f.get(f);
            if (value != null) {
                retval = value;
            } else {
                retval = "NULL";
            }
        } catch (Exception e) {
            System.err.println("No such field: " + e.getMessage());
        }
    }
    return retval;
}
2
  • 1
    I don't think that Java is the right tool for that, Groovy would be better suited IMO. Commented Feb 23, 2010 at 18:27
  • 1
    Personally I don't see the problem with having a Java model that maps to your database model. Also when creating SQL queries, make sure to use PreparedStatements to avoid SQL Injection, rather than building SQL strings. Commented Feb 23, 2010 at 18:45

5 Answers 5

35

It is possible to generate classes (via cglib, asm, javassist, bcel), but you shouldn't do it that way. Why?

  • the code that's using the library should expect type Object and get all the fields using reflection - not a good idea
  • java is statically typed language, and you want to introduce dynamic typing - it's not the place.

If you simply want the data in an undefined format, then you can return it in an array, like Object[], or Map<String, Object> if you want them named, and get it from there - it will save you much trouble with unneeded class generation for the only purpose of containing some data that will be obtained by reflection.

What you can do instead is have predefined classes that will hold the data, and pass them as arguments to querying methods. For example:

 public <T> T executeQuery(Class<T> expectedResultClass, 
      String someArg, Object.. otherArgs) {..}

Thus you can use reflection on the passed expectedResultClass to create a new object of that type and populate it with the result of the query.

That said, I think you could use something existing, like an ORM framework (Hibernate, EclipseLink), spring's JdbcTemplate, etc.

Sign up to request clarification or add additional context in comments.

8 Comments

My idea was to avoid having a large number of classes that only have simple fields in them. It would be much simpler to create the classes on the fly. Much less code and just about as clear as having all those classes.
@Makis but how will you be using the generated classes? You can't cast to them, you can't know what their fields are, when writing the code.
As I wrote, I will use reflection to determine the fields of that class - that's all the info I need. I will look at the type, name and value of each field. I'll add the example to my question above.
@Makis check my update - if the data you will be using doesn't need to have any predefined form, you can just fill an object array (Object[]), instead of all the trouble with class generation and reflection
If you need field names mapped to values, why not use a HashMap<String, Object>?
|
34

There are many different ways to achieve this (e.g proxies, ASM), but the simplest approach, one that you can start with when prototyping is:

import java.io.*;
import java.util.*;
import java.lang.reflect.*;

public class MakeTodayClass {
  Date today = new Date();
  String todayMillis = Long.toString(today.getTime());
  String todayClass = "z_" + todayMillis;
  String todaySource = todayClass + ".java";

  public static void main (String args[]){
    MakeTodayClass mtc = new MakeTodayClass();
    mtc.createIt();
    if (mtc.compileIt()) {
       System.out.println("Running " + mtc.todayClass + ":\n\n");
       mtc.runIt();
       }
    else
       System.out.println(mtc.todaySource + " is bad.");
    }

  public void createIt() {
    try {
      FileWriter aWriter = new FileWriter(todaySource, true);
      aWriter.write("public class "+ todayClass + "{");
      aWriter.write(" public void doit() {");
      aWriter.write(" System.out.println(\""+todayMillis+"\");");
      aWriter.write(" }}\n");
      aWriter.flush();      
      aWriter.close();
      }
    catch(Exception e){
      e.printStackTrace();
      }
    }

  public boolean compileIt() {
    String [] source = { new String(todaySource)};
    ByteArrayOutputStream baos= new ByteArrayOutputStream();

    new sun.tools.javac.Main(baos,source[0]).compile(source);
    // if using JDK >= 1.3 then use
    //   public static int com.sun.tools.javac.Main.compile(source);    
    return (baos.toString().indexOf("error")==-1);
    }

  public void runIt() {
    try {
      Class params[] = {};
      Object paramsObj[] = {};
      Class thisClass = Class.forName(todayClass);
      Object iClass = thisClass.newInstance();
      Method thisMethod = thisClass.getDeclaredMethod("doit", params);
      thisMethod.invoke(iClass, paramsObj);
      }
    catch (Exception e) {
      e.printStackTrace();
      }
    }
}

8 Comments

I can't find sun.tools.javac.Main nor com.sun.tools.javac. Where could I find these?
Try : import com.sun.tools.javac.Main and tell me if that works
There is the Java Compiler API aka JSR-199 for this now (classes from javax.tool.*). But I don't think this is the right path for the OP.
@AmirAfghani I was unable to use that. This import (import com.sun.tools.javac.Main) is returning package doesn't exist.
I'd recommend using JOOR for this, it supports Java 9+ to. github.com/jOOQ/jOOR
|
4

Recently I needed to create about 200 simple classes from medatata (objects filled with static data) and I did it through the open source burningwave library, with the following scenario:

  • The classes needed to have a certain prefix in the name, for example "Registro "*.java;
  • The classes needed to extend from a superclass Registro.java
  • The classes needed to contain JPA annotations like @Entity, @Column (in attributes), Lombok annotations and custom annotations.

Here is the link to the repository with the complete project: https://github.com/leandrosoares6/criacao-classes-entidade-efd

Here is the code snippet responsible for creating the classes:

public class RegistrosClassFactory {
    private static final String PACOTE = "com.example.demo.model.registros";
    private static final String SCHEMA = "MY_SCHEMA";
    private static final String PREFIXO = "Registro";

    static void criaRegistros() {
        List<RegistroTest> registros = RegistroMetadataFactory.criaMetadados();
        criaClasses(registros);
    }

    private static void criaClasses(List<RegistroTest> registros) {
        for (RegistroTest registroTest : registros) {
            UnitSourceGenerator gerador = UnitSourceGenerator.create(PACOTE);
            ClassSourceGenerator registro = ClassSourceGenerator
                    .create(TypeDeclarationSourceGenerator.create(PREFIXO + registroTest.getNome()))
                    .addModifier(Modifier.PUBLIC)
                    .addAnnotation(AnnotationSourceGenerator.create(Getter.class))
                    .addAnnotation(AnnotationSourceGenerator.create(Setter.class))
                    .addAnnotation(AnnotationSourceGenerator.create(NoArgsConstructor.class))
                    .addAnnotation(AnnotationSourceGenerator.create(ToString.class))
                    .addAnnotation(AnnotationSourceGenerator.create(Entity.class))
                    .addAnnotation(AnnotationSourceGenerator.create(Table.class)
                            .addParameter("name",
                                    VariableSourceGenerator.create(String.format("\"%s\"",
                                            registroTest.getNomeTabelaBd())))
                            .addParameter("schema", VariableSourceGenerator
                                    .create(String.format("\"%s\"", SCHEMA))));

            criaColunas(registroTest.getCampos(), registro);

            registro.addConstructor(FunctionSourceGenerator.create().addModifier(Modifier.PUBLIC)
                    .addParameter(VariableSourceGenerator.create(String.class, "linha"))
                    .addBodyCodeLine("super(linha);")).expands(Registro.class);
            gerador.addClass(registro);

            // System.out.println("\nRegistro gerado:\n" + gerador.make());
            String caminhoPastaRegistros = System.getProperty("user.dir") + "/src/main/java/";
            gerador.storeToClassPath(caminhoPastaRegistros);
        }
    }

    private static void criaColunas(List<Campo> campos, ClassSourceGenerator registro) {
        for (Campo campo : campos) {
            VariableSourceGenerator field = VariableSourceGenerator
                    .create(TypeDeclarationSourceGenerator.create(String.class),
                            campo.getNomeAtributo())
                    .addModifier(Modifier.PRIVATE)
                    .addAnnotation(AnnotationSourceGenerator.create(Column.class)
                            .addParameter("name", VariableSourceGenerator
                                    .create(String.format("\"%s\"", campo.getNome())))

                    )
                    .addAnnotation(AnnotationSourceGenerator.create(Indice.class).addParameter(
                            "valor",
                            VariableSourceGenerator.create(String.valueOf(campo.getSequencial()))));

            if (campo.getNome().equals("ID")) {
                field.addAnnotation(AnnotationSourceGenerator.create(Id.class));
            }
            if (campo.getEId()) {
                field.addAnnotation(AnnotationSourceGenerator.create(CampoTipoId.class));
            }
            if (campo.getEData()) {
                field.addAnnotation(AnnotationSourceGenerator.create(CampoTipoData.class));
            }
            if (campo.getEDataPart()) {
                field.addAnnotation(AnnotationSourceGenerator.create(CampoTipoDataPart.class));
            }

            registro.addField(field);
        }
    }
}

Comments

3

This is possible, but (I believe) you need something like ASM or BCEL.

Alternately, you could use something with more power (like Groovy).

Comments

2

It will take a couple of minutes to create a data model class for each table, which you can easily map to the database with an ORM like Hibernate or by writing your own JDBC DAOs. It is far easier than delving deeply into reflection.

You could create a utility that interrogates the database structure for a table, and creates the data model class and DAO for you. Alternatively you could create the model in Java and create a utility to create the database schema and DAO from that (using reflection and Java 5 Annotations to assist). Don't forget that javaFieldNames are different from database_column_names typically.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.