Rapid CRUD development in Java: downshifting with 1C: Enterprise
In connection with the latest events on the world stage and the depreciation of the national currency, difficult times are coming for programmers at 1C: Enterprise. Many are fired, at the same time, competition is increasing on the part of newcomers, who have appeared on the market quite a lot - you won’t complain about it, since working as a teacher in a training center at MSTU im. Bauman, I myself had a hand in this, issuing evidence with a generous hand.
At the same time, prospects are opening up for the development of other languages, since work for a foreign customer suddenly became profitable again. Also, interest in open source software at all levels of the technological stack has increased, and most of all, in “import-substituting” DBMSs such as PostgreSQL, MySQL.
Having once again found myself at a cross-project fork, I got some free time to talk about my experience in implementing several Java projects, and about what it was like after many years of development on 1C. There is a reason to listen, if only because the number of views of the Java developer’s resume, according to my estimates, is now 5 times more than 1Snik’s resume.
I want to tell by example 2 of my OpenSource projects laid out on GitHub:
No. 1. Implements the basic functionality of rapid development, available in 1C .
No. 2. It implements a reporting mechanism with user settings of the “pivot table” type, a simplified analogue of access control systems (data composition systems in 1C) .
For starters, on the first draft. I started creating a database for one organization. And very soon I met with the first obstacles. It’s not that it was so difficult to master the banal CRUD - I knew Java, but it wasn’t in the language: Vitruz was born - a virtuoso and you will die, even though Mussorgsky was drunk - you won’t drink genius.
But ... how to reach the previous development speed? In small 1C projects, you have to constantly modify the database, adding details, entities, and customer requirements often change during the game. And being a “lazy 1Snick” (c) I’m somehow used to adding an entity or making changes to an entity, I can press 1 (one) button, after which the database will be restructured, the program will start, and the changes can be seen in the form of a list , and in the form of an element, independently generated by the platform. If the attribute refers to a new directory, new forms of the list, selection, element will be automatically created for it.
What can be said about Java ... In fact, you can add props to the class code - Eclipse makes it possible to create a getter and setter in semi-automatic mode, and after the mentioned 1 button (F11) the database under ORM Hibernate is really supplemented with a new table or a new column ( if you enable hibernate.hbm2ddl.auto = update, although many are against this approach, it’s clear that we’ll turn it off on production).
Let's look at an example. Suppose we had the “Contacts” class, and we decided to add the “Contact Status” attribute to it, the list of which will be stored in a separate directory, which must be immediately given to the user to edit. Then we make changes to the class (the changes are marked with “pluses”, getters and setters are created by Eclipse):
Well, we add the essence “Statuses” itself, simple as a log (I usually copy some and change the names):
If you drop the code created by Eclipse, there will be very little left, and since working with text is faster than running through the menus in the 1C: Enterprise Configurator, I think the result is already good. This is me about the database.
But what about the interface?
In the Java community, the Swing UI framework is popular. But all the examples for it performed specific tasks, for some reason I did not find a universal solution that could be used everywhere.
There was no corny decent form editor. NetBeans is not bad, but it blocks the configuration code, which is terribly annoying, and in Eclipse the windowsBilder plugin constantly re-reads this code, and it slows down incredibly - and often crashes. Plus, inside the anonymous classes of handlers created by him, the remaining elements of the form are not visible - an ingenious move. For some reason, IDEA was even more annoying to slow down - although it would seem that the SSD on M.2 should have solved such problems.
As a result, the task “add an entity and drop it onto a form” requires a lot of code and time, compared to 1C.
They will ask: “a lot” - this is how much, have you snickered, my friend?
I will answer. Carrying out trainings for RP, as a preface to one of the blocks from scratch, I made the simplest inventory control loop in 4 minutes 35 seconds (4:35, Karl!), Including entering a test case from two documents into the database. The training participants measured the time, and then I asked them with pathos - if everything is so simple, where are the millions of budgets spent?
But I had no idea how to achieve a comparable development speed of the finished application, even using a project template with the DBMS drivers already connected (PostgreSQL, MySQL, Oracle DB or whatever), ORM Hibernate and Swing (in fact, Swingx - in pure Swing no decent elements like JXTreeTable). Obviously, the template should include powerful customizer classes. And I decided to make them, on my knee.
If you draw a parallel with 1C, the table field tied to the form requisite (for example, a dynamic list) is JXTable or JXTreeTable tied to the so-called TableModel. Without thinking twice, I inherited from the standard table and tree model, added the entity class with which we are working and the query text:
Most importantly, JXTable can call the getValueAt (int row, int col) method on its TableModel. As for JXTreeTable, there it is getValueAt (ArrayNode node, col), where node is the public class ArrayNode extends DefaultMutableTreeTableNode is the top of the tree.
According to the qtext request, the data is pulled from the database, this can happen either immediately in a large piece, or as necessary (when calling getValueAt), with the implementation of the cache and paging. There is no paging in the demo, it is implemented through the next added annotation, for example @Paging, and the addition of model classes with a buffer.
The output of the table should contain this data, but the user needs to display a limited list of columns with the correct names and a certain width. How to get Russian names to be set by default? .. For this, when describing entity, in addition to persistent annotations, I also had to get my own Synonym annotation, which you already saw in the listing (and the version on GitHub already supports on-line switching between any number languages by specifying text, textEng, textDe, and adding in the global props element the language “Eng”, “De” ...).
In order not to get up twice, I added the name of the form classes to the Entity with the annotation Forms - element, list, select. It is clear that these must be descendants of the JFrame.
But the task was to make the program, like the 1C: Enterprise platform, itself create default forms (otherwise what 4:35 to a new entity), therefore, in the example in the Contacts class, the annotation is empty - @Forms (element = ""), although earlier referenced the VContacts form class.
As colleagues have already guessed, the creation of columns in BeanTableModel, as well as the creation of attributes in the automatically generated forms of elements of our entities, occurs with the help of reflection, that is, working with entity metadata. I immediately came across 2 ways to iterate over metadata elements: in the usual way (in the FormElement class) and through the Introspector (in models).
So, we brought the data to the table using the model. We can say that such a table is the basis of the list or selection form. Now it would be nice to implement its behavior - opening the form of an element (adding and editing), deleting, selecting, and so on.

We managed to connect the actions of the buttons on the JToolbar with the table in an extremely ugly way - by scanning the elements inside the common parent. Horrified, I put this in a separate ut class (from the word “utilities”) - so as not to see anymore. But the goal was achieved.
My bike allowed:
1. Continue to use the form editor to arrange elements on the form.
2. To configure the behavior of these elements in small blocks of code (xml would do here, but I don’t like the large number of files, I get confused in them):
And for input fields with selection buttons, the setting is 1 line of the type:
3. If laziness, you can not create any forms for secondary entities at all, so that the program makes them automatically, based on the classes themselves and annotations. And these forms can be opened, even if there are 20 links, the CRUD functionality “Select, Add, Edit, Delete, Refresh” will work. This means that the student will be able to slap his laboratory work with the base for the library in 10 minutes without coming to creation.

What next? .. Java provides much more extensive features than the built-in 1C language, but it is more concise. Since most objects (ArrayList, HashMap and others) are the same, in principle you can even try to write a certain interpreter, but I’m much more interested in expanding the SQL query language with dereferencing, totals, and virtual tables. Obviously, such a task is no longer solved through ORM.
The second article will talk about the project of a tool for flexible generation of custom reports, which is just in tune with the topic of queries, so there it will be discussed in more detail.
See you again.
At the same time, prospects are opening up for the development of other languages, since work for a foreign customer suddenly became profitable again. Also, interest in open source software at all levels of the technological stack has increased, and most of all, in “import-substituting” DBMSs such as PostgreSQL, MySQL.
Having once again found myself at a cross-project fork, I got some free time to talk about my experience in implementing several Java projects, and about what it was like after many years of development on 1C. There is a reason to listen, if only because the number of views of the Java developer’s resume, according to my estimates, is now 5 times more than 1Snik’s resume.
I want to tell by example 2 of my OpenSource projects laid out on GitHub:
No. 1. Implements the basic functionality of rapid development, available in 1C .
No. 2. It implements a reporting mechanism with user settings of the “pivot table” type, a simplified analogue of access control systems (data composition systems in 1C) .
For starters, on the first draft. I started creating a database for one organization. And very soon I met with the first obstacles. It’s not that it was so difficult to master the banal CRUD - I knew Java, but it wasn’t in the language: Vitruz was born - a virtuoso and you will die, even though Mussorgsky was drunk - you won’t drink genius.
But ... how to reach the previous development speed? In small 1C projects, you have to constantly modify the database, adding details, entities, and customer requirements often change during the game. And being a “lazy 1Snick” (c) I’m somehow used to adding an entity or making changes to an entity, I can press 1 (one) button, after which the database will be restructured, the program will start, and the changes can be seen in the form of a list , and in the form of an element, independently generated by the platform. If the attribute refers to a new directory, new forms of the list, selection, element will be automatically created for it.
What can be said about Java ... In fact, you can add props to the class code - Eclipse makes it possible to create a getter and setter in semi-automatic mode, and after the mentioned 1 button (F11) the database under ORM Hibernate is really supplemented with a new table or a new column ( if you enable hibernate.hbm2ddl.auto = update, although many are against this approach, it’s clear that we’ll turn it off on production).
Let's look at an example. Suppose we had the “Contacts” class, and we decided to add the “Contact Status” attribute to it, the list of which will be stored in a separate directory, which must be immediately given to the user to edit. Then we make changes to the class (the changes are marked with “pluses”, getters and setters are created by Eclipse):
The code
Entity
Table (name = "contacts")
@Synonym (text = "Contacts")
@Forms (element = "")
public class Contacts implements java.io.Serializable {
@Synonym (text = "Code")
private int id;
@Synonym (text = "Last Name")
private String f;
@Synonym (text = “Name”)
private String i;
@Synonym (text = "Middle Name")
private String o;
@Synonym (text = “Status”) // +++++++++++++++++++++++++++
private Contact_Status status; // ++++++++++++++++++++++++++++
@ Synonym (text = "Address")
private String address;
@Synonym (text = “Phone”)
private String phone;
@Synonym (text = "Other")
private String description;
public Contacts () {
}
public Contacts (int id, String f, String i, String o,
Contact_Status status, // +++++++++++++++++++++++++ ++
String address, String phone, String description) {
this.id = id;
this.f = f;
this.i = i;
this.o = o;
this.status = status; // ++++++++++++++++++++++++++++
this.address = address;
this.phone = phone;
this.description = description;
}
Id
@GeneratedValue (strategy = GenerationType.IDENTITY)
@Column (name = "id")
public int getId () {
return this.id;
}
public void setId (int id) {
this.id = id;
}
@Synonym (text = "Last Name")
public String getF () {
return this.f;
}
public void setF (String f) {
this.f = f;
}
public String getI () {
return this.i;
}
public void setI (String i) {
this.i = i;
}
public String getO () {
return this.o;
}
public void setO (String o) {
this.o = o;
}
// {++++++++++++++++++++++++++++
@ManyToOne (targetEntity = Contact_Status.class, cascade = {CascadeType.ALL}
NotFound (action = NotFoundAction.IGNORE)
@JoinColumn (name = "contact_status", referencedColumnName = "id", nullable = true, insertable = false, updatable = true)
// by Eclipse
public Contact_Status getStatus () {
return this.status;
}
public void setStatus (Contact_Status status) {
this.status = status;
}
//} +++++++++++++++++++++++++++++
public String getAddress () {
return this.address;
}
public void setAddress (String address) {
this.address = address;
}
public String getPhone () {
return this.phone;
}
public void setPhone (String phone) {
this.phone = phone;
}
public String getDescription () {
return this.description;
}
public void setDescription (String description) {
this.description = description;
}
public String toString () {
return this.f + "" + this.i + "" + this.o;
}
}
Table (name = "contacts")
@Synonym (text = "Contacts")
@Forms (element = "")
public class Contacts implements java.io.Serializable {
@Synonym (text = "Code")
private int id;
@Synonym (text = "Last Name")
private String f;
@Synonym (text = “Name”)
private String i;
@Synonym (text = "Middle Name")
private String o;
@Synonym (text = “Status”) // +++++++++++++++++++++++++++
private Contact_Status status; // ++++++++++++++++++++++++++++
@ Synonym (text = "Address")
private String address;
@Synonym (text = “Phone”)
private String phone;
@Synonym (text = "Other")
private String description;
public Contacts () {
}
public Contacts (int id, String f, String i, String o,
Contact_Status status, // +++++++++++++++++++++++++ ++
String address, String phone, String description) {
this.id = id;
this.f = f;
this.i = i;
this.o = o;
this.status = status; // ++++++++++++++++++++++++++++
this.address = address;
this.phone = phone;
this.description = description;
}
Id
@GeneratedValue (strategy = GenerationType.IDENTITY)
@Column (name = "id")
public int getId () {
return this.id;
}
public void setId (int id) {
this.id = id;
}
@Synonym (text = "Last Name")
public String getF () {
return this.f;
}
public void setF (String f) {
this.f = f;
}
public String getI () {
return this.i;
}
public void setI (String i) {
this.i = i;
}
public String getO () {
return this.o;
}
public void setO (String o) {
this.o = o;
}
// {++++++++++++++++++++++++++++
@ManyToOne (targetEntity = Contact_Status.class, cascade = {CascadeType.ALL}
NotFound (action = NotFoundAction.IGNORE)
@JoinColumn (name = "contact_status", referencedColumnName = "id", nullable = true, insertable = false, updatable = true)
// by Eclipse
public Contact_Status getStatus () {
return this.status;
}
public void setStatus (Contact_Status status) {
this.status = status;
}
//} +++++++++++++++++++++++++++++
public String getAddress () {
return this.address;
}
public void setAddress (String address) {
this.address = address;
}
public String getPhone () {
return this.phone;
}
public void setPhone (String phone) {
this.phone = phone;
}
public String getDescription () {
return this.description;
}
public void setDescription (String description) {
this.description = description;
}
public String toString () {
return this.f + "" + this.i + "" + this.o;
}
}
Well, we add the essence “Statuses” itself, simple as a log (I usually copy some and change the names):
The code
// {++++++++++++++++++++++++++++
Entity
Table (name = “contact_status”)
@Synonym (text = “Statuses”)
@Forms ( element = "")
public class Contact_Status implements java.io.Serializable {
@Synonym (text = "Code")
private int id;
@Synonym (text = "Name")
private String name;
Id
@GeneratedValue (strategy = GenerationType.IDENTITY)
@Column (name = "id")
// by Eclipse
public int getId () {
return id;
}
public void setId (int id) {
this.id = id;
}
public String getName () {
return name;
}
public void setName (String name) {
this.name = name;
}
public String toString () {
return name;
}
}
//} ++++++++++++++++++++++++++++
Entity
Table (name = “contact_status”)
@Synonym (text = “Statuses”)
@Forms ( element = "")
public class Contact_Status implements java.io.Serializable {
@Synonym (text = "Code")
private int id;
@Synonym (text = "Name")
private String name;
Id
@GeneratedValue (strategy = GenerationType.IDENTITY)
@Column (name = "id")
// by Eclipse
public int getId () {
return id;
}
public void setId (int id) {
this.id = id;
}
public String getName () {
return name;
}
public void setName (String name) {
this.name = name;
}
public String toString () {
return name;
}
}
//} ++++++++++++++++++++++++++++
If you drop the code created by Eclipse, there will be very little left, and since working with text is faster than running through the menus in the 1C: Enterprise Configurator, I think the result is already good. This is me about the database.
But what about the interface?
In the Java community, the Swing UI framework is popular. But all the examples for it performed specific tasks, for some reason I did not find a universal solution that could be used everywhere.
There was no corny decent form editor. NetBeans is not bad, but it blocks the configuration code, which is terribly annoying, and in Eclipse the windowsBilder plugin constantly re-reads this code, and it slows down incredibly - and often crashes. Plus, inside the anonymous classes of handlers created by him, the remaining elements of the form are not visible - an ingenious move. For some reason, IDEA was even more annoying to slow down - although it would seem that the SSD on M.2 should have solved such problems.
As a result, the task “add an entity and drop it onto a form” requires a lot of code and time, compared to 1C.
They will ask: “a lot” - this is how much, have you snickered, my friend?
I will answer. Carrying out trainings for RP, as a preface to one of the blocks from scratch, I made the simplest inventory control loop in 4 minutes 35 seconds (4:35, Karl!), Including entering a test case from two documents into the database. The training participants measured the time, and then I asked them with pathos - if everything is so simple, where are the millions of budgets spent?
But I had no idea how to achieve a comparable development speed of the finished application, even using a project template with the DBMS drivers already connected (PostgreSQL, MySQL, Oracle DB or whatever), ORM Hibernate and Swing (in fact, Swingx - in pure Swing no decent elements like JXTreeTable). Obviously, the template should include powerful customizer classes. And I decided to make them, on my knee.
If you draw a parallel with 1C, the table field tied to the form requisite (for example, a dynamic list) is JXTable or JXTreeTable tied to the so-called TableModel. Without thinking twice, I inherited from the standard table and tree model, added the entity class with which we are working and the query text:
public class BeanTableModel extends DefaultTableModel{
...
private Class beanClass; //Класс сущности, "Основная таблица" динамического списка 1С
private String qtext; //Текст запроса динамического списка 1С
...
}
Most importantly, JXTable can call the getValueAt (int row, int col) method on its TableModel. As for JXTreeTable, there it is getValueAt (ArrayNode node, col), where node is the public class ArrayNode extends DefaultMutableTreeTableNode is the top of the tree.
According to the qtext request, the data is pulled from the database, this can happen either immediately in a large piece, or as necessary (when calling getValueAt), with the implementation of the cache and paging. There is no paging in the demo, it is implemented through the next added annotation, for example @Paging, and the addition of model classes with a buffer.
The output of the table should contain this data, but the user needs to display a limited list of columns with the correct names and a certain width. How to get Russian names to be set by default? .. For this, when describing entity, in addition to persistent annotations, I also had to get my own Synonym annotation, which you already saw in the listing (and the version on GitHub already supports on-line switching between any number languages by specifying text, textEng, textDe, and adding in the global props element the language “Eng”, “De” ...).
In order not to get up twice, I added the name of the form classes to the Entity with the annotation Forms - element, list, select. It is clear that these must be descendants of the JFrame.
But the task was to make the program, like the 1C: Enterprise platform, itself create default forms (otherwise what 4:35 to a new entity), therefore, in the example in the Contacts class, the annotation is empty - @Forms (element = ""), although earlier referenced the VContacts form class.
As colleagues have already guessed, the creation of columns in BeanTableModel, as well as the creation of attributes in the automatically generated forms of elements of our entities, occurs with the help of reflection, that is, working with entity metadata. I immediately came across 2 ways to iterate over metadata elements: in the usual way (in the FormElement class) and through the Introspector (in models).
So, we brought the data to the table using the model. We can say that such a table is the basis of the list or selection form. Now it would be nice to implement its behavior - opening the form of an element (adding and editing), deleting, selecting, and so on.

We managed to connect the actions of the buttons on the JToolbar with the table in an extremely ugly way - by scanning the elements inside the common parent. Horrified, I put this in a separate ut class (from the word “utilities”) - so as not to see anymore. But the goal was achieved.
My bike allowed:
1. Continue to use the form editor to arrange elements on the form.
2. To configure the behavior of these elements in small blocks of code (xml would do here, but I don’t like the large number of files, I get confused in them):
ContactTable.setTreeTableModel(new BeanTreeTableModel("select * from contacts", null, Contacts.class, new ArrayNode(new Object[0])));
ArrayList tt = new ArrayList< >();
tt.add(ut.newHashMap(new ArrayList < >(Arrays.asList("name,title,width", "id", "№", 60))));
...
tt.add(ut.newHashMap(new ArrayList< >(Arrays.asList("name,title,width", "phone", "Телефон", 200))));
ut.TuneTreeColumns(ContactTable, tt);
ut.TuneToolbar(Contacts_toolBar, ContactTable);
And for input fields with selection buttons, the setting is 1 line of the type:
ut.linkFormObjectElements(FormElement.this,d.getName(),t1,t2);
3. If laziness, you can not create any forms for secondary entities at all, so that the program makes them automatically, based on the classes themselves and annotations. And these forms can be opened, even if there are 20 links, the CRUD functionality “Select, Add, Edit, Delete, Refresh” will work. This means that the student will be able to slap his laboratory work with the base for the library in 10 minutes without coming to creation.

What next? .. Java provides much more extensive features than the built-in 1C language, but it is more concise. Since most objects (ArrayList, HashMap and others) are the same, in principle you can even try to write a certain interpreter, but I’m much more interested in expanding the SQL query language with dereferencing, totals, and virtual tables. Obviously, such a task is no longer solved through ORM.
The second article will talk about the project of a tool for flexible generation of custom reports, which is just in tune with the topic of queries, so there it will be discussed in more detail.
See you again.