📜 ⬆️ ⬇️

Rapid Java CRUD Development: Downshifting with 1C: Enterprise

In connection with the latest events in the world arena and the depreciation of the national currency, for programmers, “1C: Enterprise” has a difficult time. Many are fired, and at the same time competition from newcomers is increasing, and quite a lot of them are appearing on the market - which you will not complain about, since working as a teacher in the training center at Moscow State Technical University. Bauman, I myself put my hand to it, issuing evidence with a generous hand.

At the same time, prospects for the development of other languages ​​are opening up, since work for a foreign customer has suddenly become profitable again. Also, interest in open source software at all levels of the technological stack, and most of all, in “import-substituting” DBMSs like PostgreSQL, MySQL, has increased.

Once again at the inter-project fork, I got some free time to talk about my experience in implementing several Java projects and what it was like after many years of development on 1C. There is a sense to listen, if only because the number of views of a Java developer’s resume, according to my estimates, is now 5 times longer than the summary of 1Snik.
')
I want to share the example of 2 of my OpenSource projects laid out on GitHub:

№1. Implements the basic functionality of rapid development, available in 1C .
№2. It implements a report generation mechanism with user settings of the “pivot table” type, a simplified analogue of the ACS (data composition system in 1C) .

For a start, on the first project. I started to create a database for one organization. And very soon met with the first obstacles. Not that mastering the banal CRUD was so difficult - I knew Java, but the matter was not in the language: I was born a vitruoz - a virtuoso and you die, even as Mussorgsky sang - you will not drink genius.

But ... how to reach the previous development speed? In small 1C projects, it is necessary to constantly modify the database, adding details, entities, and customer requirements often change as the game progresses. And being a “lazy 1Sniki” (c), I somehow got used to that by adding an entity or making changes to an entity, you can press 1 (one) button, after which the database will be restructured, the program will be started, 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 requisite refers to a new directory, new forms of the list, the choice, the element will be automatically created for it.

What can you say about Java ... In fact, you can add props to the class code - Eclipse allows you to create a getter and a setter in semi-automatic mode, and after the mentioned 1 button (F11), the database under ORM Hibernate will indeed be 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 at production).

Let's take an example. Suppose we had a class “Contacts”, and we decided to add the “Contact status” attribute to it, the list of which will be stored in a separate directory, which we should immediately give to the user to edit. Then we make changes to the class (the changes are marked with “pluses”, the getters and setters are created by Eclipse):

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 = "Patronymic")
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 very entity “Statuses”, simple as a log (I usually copy some and change the names):

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;
}
}
//} ++++++++++++++++++++++++++

If we discard the code created by Eclipse, it will remain quite a bit, and since I work with text faster than running through the menu in the 1C: Configurator, I think that the result is already quite good. This is me about the database.

But what about the interface?

The Java community is popular with the Swing UI framework. But all the examples for it performed specific tasks, for some reason I could 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 rest of the form elements are not visible - a brilliant move. For some reason, IDEA was even more annoying when braking, although it would seem that the SSD on M.2 should have solved these problems.

As a result, the task “add an entity and drop it on a form” requires a lot of code and time, compared to 1C.

They will ask: “many” - this is how much, haven't you, my dear?

I will answer. Conducting trainings for the RP, I, as a preface to one of the blocks from scratch, made the simplest inventory accounting contour in 4 minutes 35 seconds (4:35, Karl!), Including entering into the database a test example from two documents. 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 comparable development speed of the finished application, even using a project template with already connected DBMS drivers (PostgreSQL, MySQL, Oracle DB or whatever you have), ORM Hibernate and Swing (in fact, Swingx is in pure Swing There are no decent elements like JXTreeTable). Obviously, the template should include powerful customization classes. And I decided to make them on my knee.

If we 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 tabular and tree model, added an entity class with which we work 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 public class ArrayNode extends DefaultMutableTreeTableNode is the top of the tree.

According to the qtext request, the data is pulled out of the database, this can occur both at once in a large chunk, and as needed (when calling getValueAt), with the implementation of the cache and paging. There is no paging in the posted 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 ensure that Russian names are set by default? .. For this, when describing an entity in addition to persistent annotations, you had to create your Synonym annotation that you have already seen in the listing (and the version on GitHub already supports on-line switching between any number languages ​​by specifying text, textEng, textDe, and adding language “Eng”, “De” ...) to global props.

In order not to get up twice, I added the name of the form classes with the Forms annotation to element, list, select. It is clear that this should be the descendants of JFrame.

But the task was to make the program, like the 1C: Enterprise platform itself, create default forms (otherwise there are 4:35 on a new entity), therefore in the example class Contacts 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 details in the automatically created forms of the elements of our entities, takes place with the help of reflection, that is, working with the metadata of the entity. I immediately stumbled upon 2 ways to iterate over metadata elements: in the usual way (in the FormElement class) and through the Introspector (in the models).

So, we brought the data to the table using the model. It can be said that such a table is the basis of a list or selection form. Now it would be nice to implement its behavior - opening the form of the element (adding and editing), deleting, selecting, and so on.

image

It was possible to connect the actions of the buttons on JToolbar with the table in an extremely ugly way - by scanning the elements inside the common parent. Horrified, I carried it out to a separate class, ut (from the word “utilities”) - in order not to see again. But the goal was achieved.

My bike allowed:

1. Continue using the form editor to position elements on the form.
2. Configure the behavior of these elements in small blocks of code (xml would work here, but I don’t like a large number of files, I’m confusing them):

 ContactTable.setTreeTableModel(new BeanTreeTableModel("select * from contacts", null, Contacts.class, new ArrayNode(new Object[0]))); ArrayList<HashMap> 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 input fields with selection buttons have a setting of 1 line of type:

 ut.linkFormObjectElements(FormElement.this,d.getName(),t1,t2); 

3. If you are lazy, you can not create any forms at all for secondary entities, so that the program does them automatically, based on the classes themselves and annotations. And these forms can open even if there are 20 links, the CRUD “Select, Add, Edit, Delete, Update” functionality 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 into creation.

image

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 an interpreter, but I’m much more interested in extending the SQL query language by dereferencing, totals, and virtual tables. Obviously, this task is not solved through ORM.

The second article will tell about the design of the tool for flexible generation of user reports, which is just in tune with the topic of requests, so there it will be discussed in more detail.

Until new meetings.

Source: https://habr.com/ru/post/269083/


All Articles